keycloak-memoizeit

Merge pull request #3008 from patriot1burke/master new

7/7/2016 3:56:38 PM

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 bf2b1c2..f077927 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/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java
index 6a4ff4f..6dcc0d9 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java
@@ -37,6 +37,7 @@ import org.keycloak.models.UserFederationMapperModel;
 import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.cache.infinispan.RealmCache;
 import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.storage.StorageProviderModel;
 
 import java.io.Serializable;
 import java.security.PrivateKey;
@@ -110,6 +111,7 @@ public class CachedRealm extends AbstractRevisioned {
 
     protected List<RequiredCredentialModel> requiredCredentials;
     protected List<UserFederationProviderModel> userFederationProviders;
+    protected List<StorageProviderModel> storageProviders;
     protected MultivaluedHashMap<String, UserFederationMapperModel> userFederationMappers = new MultivaluedHashMap<String, UserFederationMapperModel>();
     protected Set<UserFederationMapperModel> userFederationMapperSet;
     protected List<IdentityProviderModel> identityProviders;
@@ -206,6 +208,7 @@ public class CachedRealm extends AbstractRevisioned {
 
         requiredCredentials = model.getRequiredCredentials();
         userFederationProviders = model.getUserFederationProviders();
+        storageProviders = model.getStorageProviders();
         userFederationMapperSet = model.getUserFederationMappers();
         for (UserFederationMapperModel mapper : userFederationMapperSet) {
             this.userFederationMappers.add(mapper.getFederationProviderId(), mapper);
@@ -598,4 +601,8 @@ public class CachedRealm extends AbstractRevisioned {
     public List<RequiredActionProviderModel> getRequiredActionProviderList() {
         return requiredActionProviderList;
     }
+
+    public List<StorageProviderModel> getStorageProviders() {
+        return storageProviders;
+    }
 }
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java
index f37ecf1..89049ca 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java
@@ -53,7 +53,6 @@ public class CachedUser extends AbstractRevisioned implements InRealm  {
     private Set<String> requiredActions = new HashSet<>();
     private Set<String> roleMappings = new HashSet<>();
     private Set<String> groups = new HashSet<>();
-    private Map<String, CachedUserConsent> consents = new HashMap<>(); // Key is client DB Id
 
 
 
@@ -82,13 +81,6 @@ public class CachedUser extends AbstractRevisioned implements InRealm  {
                 groups.add(group.getId());
             }
         }
-
-        List<UserConsentModel> consents = user.getConsents();
-        if (consents != null) {
-            for (UserConsentModel consent : consents) {
-                this.consents.put(consent.getClient().getId(), new CachedUserConsent(consent));
-            }
-        }
     }
 
     public String getRealm() {
@@ -155,7 +147,4 @@ public class CachedUser extends AbstractRevisioned implements InRealm  {
         return groups;
     }
 
-    public Map<String, CachedUserConsent> getConsents() {
-        return consents;
-    }
 }
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUserConsents.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUserConsents.java
new file mode 100644
index 0000000..eaca957
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUserConsents.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.models.cache.infinispan.entities;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserConsentModel;
+
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class CachedUserConsents extends AbstractRevisioned implements InRealm {
+    private HashMap<String, CachedUserConsent> consents = new HashMap<>();
+    private final String realmId;
+
+    public CachedUserConsents(Long revision, String id, RealmModel realm,
+                              List<UserConsentModel> consents) {
+        super(revision, id);
+        this.realmId = realm.getId();
+        if (consents != null) {
+            for (UserConsentModel consent : consents) {
+                this.consents.put(consent.getClient().getId(), new CachedUserConsent(consent));
+            }
+        }
+    }
+
+    @Override
+    public String getRealm() {
+        return realmId;
+    }
+
+
+    public HashMap<String, CachedUserConsent> getConsents() {
+        return consents;
+    }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
index 4ea71c7..69f0510 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
@@ -23,6 +23,7 @@ import org.keycloak.models.*;
 import org.keycloak.models.cache.CacheRealmProvider;
 import org.keycloak.models.cache.infinispan.entities.CachedRealm;
 import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.storage.StorageProviderModel;
 
 import java.security.Key;
 import java.security.PrivateKey;
@@ -746,6 +747,48 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
+    public StorageProviderModel addStorageProvider(StorageProviderModel provider) {
+        getDelegateForUpdate();
+        return updated.addStorageProvider(provider);
+    }
+
+    @Override
+    public void updateStorageProvider(StorageProviderModel provider) {
+        getDelegateForUpdate();
+        updated.updateStorageProvider(provider);
+
+    }
+
+    @Override
+    public void removeStorageProvider(StorageProviderModel provider) {
+        getDelegateForUpdate();
+        updated.removeStorageProvider(provider);
+
+    }
+
+    @Override
+    public void setStorageProviders(List<StorageProviderModel> providers) {
+        getDelegateForUpdate();
+        updated.setStorageProviders(providers);
+
+    }
+
+    @Override
+    public List<StorageProviderModel> getStorageProviders() {
+        if (isUpdated()) return updated.getStorageProviders();
+        return cached.getStorageProviders();
+    }
+
+    @Override
+    public StorageProviderModel getStorageProvider(String id) {
+        if (isUpdated()) return updated.getStorageProvider(id);
+        for (StorageProviderModel model : cached.getStorageProviders()) {
+            if (model.getId().equals(id)) return model;
+        }
+        return null;
+    }
+
+    @Override
     public String getLoginTheme() {
         if (isUpdated()) return updated.getLoginTheme();
         return cached.getLoginTheme();
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 2b02cee..d697ad2 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
@@ -389,70 +389,6 @@ public class UserAdapter implements UserModel {
     }
 
     @Override
-    public void addConsent(UserConsentModel consent) {
-        getDelegateForUpdate();
-        updated.addConsent(consent);
-    }
-
-    @Override
-    public UserConsentModel getConsentByClient(String clientId) {
-        if (updated != null) return updated.getConsentByClient(clientId);
-        CachedUserConsent cachedConsent = cached.getConsents().get(clientId);
-        if (cachedConsent == null) {
-            return null;
-        }
-
-        return toConsentModel(cachedConsent);
-    }
-
-    @Override
-    public List<UserConsentModel> getConsents() {
-        if (updated != null) return updated.getConsents();
-        Collection<CachedUserConsent> cachedConsents = cached.getConsents().values();
-
-        List<UserConsentModel> result = new LinkedList<>();
-        for (CachedUserConsent cachedConsent : cachedConsents) {
-            UserConsentModel consent = toConsentModel(cachedConsent);
-            if (consent != null) {
-                result.add(consent);
-            }
-        }
-        return result;
-    }
-
-    @Override
-    public void updateConsent(UserConsentModel consent) {
-        getDelegateForUpdate();
-        updated.updateConsent(consent);
-    }
-
-    @Override
-    public boolean revokeConsentForClient(String clientId) {
-        getDelegateForUpdate();
-        return updated.revokeConsentForClient(clientId);
-    }
-
-    private UserConsentModel toConsentModel(CachedUserConsent cachedConsent) {
-        ClientModel client = keycloakSession.realms().getClientById(cachedConsent.getClientDbId(), realm);
-        if (client == null) {
-            return null;
-        }
-
-        UserConsentModel consentModel = new UserConsentModel(client);
-
-        for (String roleId : cachedConsent.getRoleIds()) {
-            RoleModel role = keycloakSession.realms().getRoleById(roleId, realm);
-            if (role != null) {
-                consentModel.addGrantedRole(role);
-            }
-        }
-        for (ProtocolMapperModel protocolMapper : cachedConsent.getProtocolMappers()) {
-            consentModel.addGrantedProtocolMapper(protocolMapper);
-        }
-        return consentModel;
-    }
-
-    @Override
     public boolean equals(Object o) {
         if (this == o) return true;
         if (o == null || !(o instanceof UserModel)) return false;
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 1a489ad..c5e4c30 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
@@ -28,6 +28,7 @@ import org.keycloak.models.KeycloakTransaction;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserConsentModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.UserModel;
@@ -35,7 +36,10 @@ import org.keycloak.models.UserProvider;
 import org.keycloak.models.cache.CacheUserProvider;
 import org.keycloak.models.cache.infinispan.entities.CachedFederatedIdentityLinks;
 import org.keycloak.models.cache.infinispan.entities.CachedUser;
+import org.keycloak.models.cache.infinispan.entities.CachedUserConsent;
+import org.keycloak.models.cache.infinispan.entities.CachedUserConsents;
 import org.keycloak.models.cache.infinispan.entities.UserListQuery;
+import org.keycloak.storage.StorageProviderModel;
 
 import java.util.*;
 
@@ -73,7 +77,7 @@ public class UserCacheSession implements CacheUserProvider {
     public UserProvider getDelegate() {
         if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction");
         if (delegate != null) return delegate;
-        delegate = session.getProvider(UserProvider.class);
+        delegate = session.userStorageManager();
 
         return delegate;
     }
@@ -342,7 +346,7 @@ public class UserCacheSession implements CacheUserProvider {
     }
 
     @Override
-    public UserModel getUserByServiceAccountClient(ClientModel client) {
+    public UserModel getServiceAccount(ClientModel client) {
         // Just an attempt to find the user from cache by default serviceAccount username
         String username = ServiceAccountConstants.SERVICE_ACCOUNT_USER_PREFIX + client.getClientId();
         UserModel user = getUserByUsername(username, client.getRealm());
@@ -350,7 +354,7 @@ public class UserCacheSession implements CacheUserProvider {
             return user;
         }
 
-        return getDelegate().getUserByServiceAccountClient(client);
+        return getDelegate().getServiceAccount(client);
     }
 
     @Override
@@ -369,6 +373,16 @@ public class UserCacheSession implements CacheUserProvider {
     }
 
     @Override
+    public List<UserModel> getUsers(RealmModel realm) {
+        return getUsers(realm, false);
+    }
+
+    @Override
+    public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults) {
+        return getUsers(realm, firstResult, maxResults, false);
+    }
+
+    @Override
     public List<UserModel> searchForUser(String search, RealmModel realm) {
         return getDelegate().searchForUser(search, realm);
     }
@@ -434,6 +448,101 @@ public class UserCacheSession implements CacheUserProvider {
     }
 
     @Override
+    public void updateConsent(RealmModel realm, UserModel user, UserConsentModel consent) {
+        invalidations.add(getConsentCacheKey(user.getId()));
+        getDelegate().updateConsent(realm, user, consent);
+    }
+
+    @Override
+    public boolean revokeConsentForClient(RealmModel realm, UserModel user, String clientInternalId) {
+        invalidations.add(getConsentCacheKey(user.getId()));
+        return getDelegate().revokeConsentForClient(realm, user, clientInternalId);
+    }
+
+    public String getConsentCacheKey(String userId) {
+        return userId + ".consents";
+    }
+
+
+    @Override
+    public void addConsent(RealmModel realm, UserModel user, UserConsentModel consent) {
+        invalidations.add(getConsentCacheKey(user.getId()));
+        getDelegate().addConsent(realm, user, consent);
+    }
+
+    @Override
+    public UserConsentModel getConsentByClient(RealmModel realm, UserModel user, String clientId) {
+        logger.tracev("getConsentByClient: {0}", user.getUsername());
+
+        String cacheKey = getConsentCacheKey(user.getId());
+        if (realmInvalidations.contains(realm.getId()) || invalidations.contains(user.getId()) || invalidations.contains(cacheKey)) {
+            return getDelegate().getConsentByClient(realm, user, clientId);
+        }
+
+        CachedUserConsents cached = cache.get(cacheKey, CachedUserConsents.class);
+
+        if (cached == null) {
+            Long loaded = cache.getCurrentRevision(cacheKey);
+            List<UserConsentModel> consents = getDelegate().getConsents(realm, user);
+            cached = new CachedUserConsents(loaded, cacheKey, realm, consents);
+            cache.addRevisioned(cached, startupRevision);
+        }
+        CachedUserConsent cachedConsent = cached.getConsents().get(clientId);
+        if (cachedConsent == null) return null;
+        return toConsentModel(realm, cachedConsent);
+    }
+
+    @Override
+    public List<UserConsentModel> getConsents(RealmModel realm, UserModel user) {
+        logger.tracev("getConsents: {0}", user.getUsername());
+
+        String cacheKey = getConsentCacheKey(user.getId());
+        if (realmInvalidations.contains(realm.getId()) || invalidations.contains(user.getId()) || invalidations.contains(cacheKey)) {
+            return getDelegate().getConsents(realm, user);
+        }
+
+        CachedUserConsents cached = cache.get(cacheKey, CachedUserConsents.class);
+
+        if (cached == null) {
+            Long loaded = cache.getCurrentRevision(cacheKey);
+            List<UserConsentModel> consents = getDelegate().getConsents(realm, user);
+            cached = new CachedUserConsents(loaded, cacheKey, realm, consents);
+            cache.addRevisioned(cached, startupRevision);
+            return consents;
+        } else {
+            List<UserConsentModel> result = new LinkedList<>();
+            for (CachedUserConsent cachedConsent : cached.getConsents().values()) {
+                UserConsentModel consent = toConsentModel(realm, cachedConsent);
+                if (consent != null) {
+                    result.add(consent);
+                }
+            }
+            return result;
+        }
+    }
+
+    private UserConsentModel toConsentModel(RealmModel realm, CachedUserConsent cachedConsent) {
+        ClientModel client = session.realms().getClientById(cachedConsent.getClientDbId(), realm);
+        if (client == null) {
+            return null;
+        }
+
+        UserConsentModel consentModel = new UserConsentModel(client);
+
+        for (String roleId : cachedConsent.getRoleIds()) {
+            RoleModel role = session.realms().getRoleById(roleId, realm);
+            if (role != null) {
+                consentModel.addGrantedRole(role);
+            }
+        }
+        for (ProtocolMapperModel protocolMapper : cachedConsent.getProtocolMappers()) {
+            consentModel.addGrantedProtocolMapper(protocolMapper);
+        }
+        return consentModel;
+    }
+
+
+    @Override
     public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) {
         UserModel user = getDelegate().addUser(realm, id, username, addDefaultRoles, addDefaultRoles);
         // just in case the transaction is rolled back you need to invalidate the user and all cache queries for that user
@@ -554,4 +663,9 @@ public class UserCacheSession implements CacheUserProvider {
     public void preRemove(ProtocolMapperModel protocolMapper) {
         getDelegate().preRemove(protocolMapper);
     }
+
+    @Override
+    public void preRemove(RealmModel realm, StorageProviderModel provider) {
+        getDelegate().preRemove(realm, provider);
+    }
 }
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/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index 5ad023a..3c9ae5f 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -136,13 +136,16 @@ public class RealmEntity {
     protected String emailTheme;
 
     @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
-    Collection<RealmAttributeEntity> attributes = new ArrayList<RealmAttributeEntity>();
+    Collection<RealmAttributeEntity> attributes = new ArrayList<>();
 
     @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
-    Collection<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>();
+    Collection<RequiredCredentialEntity> requiredCredentials = new ArrayList<>();
 
     @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
-    List<UserFederationProviderEntity> userFederationProviders = new ArrayList<UserFederationProviderEntity>();
+    List<UserFederationProviderEntity> userFederationProviders = new ArrayList<>();
+
+    @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
+    List<StorageProviderEntity> storageProviders = new ArrayList<>();
 
     @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
     Collection<UserFederationMapperEntity> userFederationMappers = new ArrayList<UserFederationMapperEntity>();
@@ -551,6 +554,14 @@ public class RealmEntity {
         this.userFederationProviders = userFederationProviders;
     }
 
+    public List<StorageProviderEntity> getStorageProviders() {
+        return storageProviders;
+    }
+
+    public void setStorageProviders(List<StorageProviderEntity> storageProviders) {
+        this.storageProviders = storageProviders;
+    }
+
     public Collection<UserFederationMapperEntity> getUserFederationMappers() {
         return userFederationMappers;
     }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/StorageProviderEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/StorageProviderEntity.java
new file mode 100755
index 0000000..f663795
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/StorageProviderEntity.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.jpa.entities;
+
+import javax.persistence.Access;
+import javax.persistence.AccessType;
+import javax.persistence.CollectionTable;
+import javax.persistence.Column;
+import javax.persistence.ElementCollection;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.MapKeyColumn;
+import javax.persistence.Table;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ * @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
+ */
+@Entity
+@Table(name="STORAGE_PROVIDER")
+public class StorageProviderEntity {
+
+    @Id
+    @Column(name="ID", length = 36)
+    @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity.  This avoids an extra SQL
+    protected String id;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "REALM_ID")
+    protected RealmEntity realm;
+
+    @Column(name="PROVIDER_NAME")
+    private String providerName;
+    @Column(name="PRIORITY")
+    private int priority;
+
+    @ElementCollection
+    @MapKeyColumn(name="name")
+    @Column(name="VALUE")
+    @CollectionTable(name="STORAGE_PROVIDER_CONFIG", joinColumns={ @JoinColumn(name="STORAGE_PROVIDER_ID") })
+    private Map<String, String> config;
+
+    @Column(name="DISPLAY_NAME")
+    private String displayName;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public RealmEntity getRealm() {
+        return realm;
+    }
+
+    public void setRealm(RealmEntity realm) {
+        this.realm = realm;
+    }
+
+    public String getProviderName() {
+        return providerName;
+    }
+
+    public void setProviderName(String providerName) {
+        this.providerName = providerName;
+    }
+
+    public int getPriority() {
+        return priority;
+    }
+
+    public void setPriority(int priority) {
+        this.priority = priority;
+    }
+
+    public Map<String, String> getConfig() {
+        return config;
+    }
+
+    public void setConfig(Map<String, String> config) {
+        this.config = config;
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public void setDisplayName(String displayName) {
+        this.displayName = displayName;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null) return false;
+        if (!(o instanceof StorageProviderEntity)) return false;
+
+        StorageProviderEntity that = (StorageProviderEntity) o;
+
+        if (!id.equals(that.getId())) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return id.hashCode();
+    }
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
index 593bae5..7802453 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
@@ -22,24 +22,32 @@ import org.keycloak.models.CredentialValidationOutput;
 import org.keycloak.models.FederatedIdentityModel;
 import org.keycloak.models.GroupModel;
 import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.ModelException;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredActionProviderModel;
 import org.keycloak.models.RoleContainerModel;
 import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserConsentModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserProvider;
 import org.keycloak.models.jpa.entities.FederatedIdentityEntity;
 import org.keycloak.models.jpa.entities.UserAttributeEntity;
+import org.keycloak.models.jpa.entities.UserConsentEntity;
+import org.keycloak.models.jpa.entities.UserConsentProtocolMapperEntity;
+import org.keycloak.models.jpa.entities.UserConsentRoleEntity;
 import org.keycloak.models.jpa.entities.UserEntity;
 import org.keycloak.models.utils.CredentialValidation;
 import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.storage.StorageProviderModel;
 
 import javax.persistence.EntityManager;
 import javax.persistence.TypedQuery;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -187,6 +195,164 @@ public class JpaUserProvider implements UserProvider {
     }
 
     @Override
+    public void addConsent(RealmModel realm, UserModel user, UserConsentModel consent) {
+        String clientId = consent.getClient().getId();
+
+        UserConsentEntity consentEntity = getGrantedConsentEntity(user, clientId);
+        if (consentEntity != null) {
+            throw new ModelDuplicateException("Consent already exists for client [" + clientId + "] and user [" + user.getId() + "]");
+        }
+
+        consentEntity = new UserConsentEntity();
+        consentEntity.setId(KeycloakModelUtils.generateId());
+        consentEntity.setUser(em.getReference(UserEntity.class, user.getId()));
+        consentEntity.setClientId(clientId);
+        em.persist(consentEntity);
+        em.flush();
+
+        updateGrantedConsentEntity(consentEntity, consent);
+    }
+
+    @Override
+    public UserConsentModel getConsentByClient(RealmModel realm, UserModel user, String clientId) {
+        UserConsentEntity entity = getGrantedConsentEntity(user, clientId);
+        return toConsentModel(realm, entity);
+    }
+
+    @Override
+    public List<UserConsentModel> getConsents(RealmModel realm, UserModel user) {
+        TypedQuery<UserConsentEntity> query = em.createNamedQuery("userConsentsByUser", UserConsentEntity.class);
+        query.setParameter("userId", user.getId());
+        List<UserConsentEntity> results = query.getResultList();
+
+        List<UserConsentModel> consents = new ArrayList<UserConsentModel>();
+        for (UserConsentEntity entity : results) {
+            UserConsentModel model = toConsentModel(realm, entity);
+            consents.add(model);
+        }
+        return consents;
+    }
+
+    @Override
+    public void updateConsent(RealmModel realm, UserModel user, UserConsentModel consent) {
+        String clientId = consent.getClient().getId();
+
+        UserConsentEntity consentEntity = getGrantedConsentEntity(user, clientId);
+        if (consentEntity == null) {
+            throw new ModelException("Consent not found for client [" + clientId + "] and user [" + user.getId() + "]");
+        }
+
+        updateGrantedConsentEntity(consentEntity, consent);
+    }
+
+    public boolean revokeConsentForClient(RealmModel realm, UserModel user, String clientId) {
+        UserConsentEntity consentEntity = getGrantedConsentEntity(user, clientId);
+        if (consentEntity == null) return false;
+
+        em.remove(consentEntity);
+        em.flush();
+        return true;
+    }
+
+
+    private UserConsentEntity getGrantedConsentEntity(UserModel user, String clientId) {
+        TypedQuery<UserConsentEntity> query = em.createNamedQuery("userConsentByUserAndClient", UserConsentEntity.class);
+        query.setParameter("userId", user.getId());
+        query.setParameter("clientId", clientId);
+        List<UserConsentEntity> results = query.getResultList();
+        if (results.size() > 1) {
+            throw new ModelException("More results found for user [" + user.getUsername() + "] and client [" + clientId + "]");
+        } else if (results.size() == 1) {
+            return results.get(0);
+        } else {
+            return null;
+        }
+    }
+
+    private UserConsentModel toConsentModel(RealmModel realm, UserConsentEntity entity) {
+        if (entity == null) {
+            return null;
+        }
+
+        ClientModel client = realm.getClientById(entity.getClientId());
+        if (client == null) {
+            throw new ModelException("Client with id " + entity.getClientId() + " is not available");
+        }
+        UserConsentModel model = new UserConsentModel(client);
+
+        Collection<UserConsentRoleEntity> grantedRoleEntities = entity.getGrantedRoles();
+        if (grantedRoleEntities != null) {
+            for (UserConsentRoleEntity grantedRole : grantedRoleEntities) {
+                RoleModel grantedRoleModel = realm.getRoleById(grantedRole.getRoleId());
+                if (grantedRoleModel != null) {
+                    model.addGrantedRole(grantedRoleModel);
+                }
+            }
+        }
+
+        Collection<UserConsentProtocolMapperEntity> grantedProtocolMapperEntities = entity.getGrantedProtocolMappers();
+        if (grantedProtocolMapperEntities != null) {
+            for (UserConsentProtocolMapperEntity grantedProtMapper : grantedProtocolMapperEntities) {
+                ProtocolMapperModel protocolMapper = client.getProtocolMapperById(grantedProtMapper.getProtocolMapperId());
+                model.addGrantedProtocolMapper(protocolMapper );
+            }
+        }
+
+        return model;
+    }
+
+    // Update roles and protocolMappers to given consentEntity from the consentModel
+    private void updateGrantedConsentEntity(UserConsentEntity consentEntity, UserConsentModel consentModel) {
+        Collection<UserConsentProtocolMapperEntity> grantedProtocolMapperEntities = consentEntity.getGrantedProtocolMappers();
+        Collection<UserConsentProtocolMapperEntity> mappersToRemove = new HashSet<UserConsentProtocolMapperEntity>(grantedProtocolMapperEntities);
+
+        for (ProtocolMapperModel protocolMapper : consentModel.getGrantedProtocolMappers()) {
+            UserConsentProtocolMapperEntity grantedProtocolMapperEntity = new UserConsentProtocolMapperEntity();
+            grantedProtocolMapperEntity.setUserConsent(consentEntity);
+            grantedProtocolMapperEntity.setProtocolMapperId(protocolMapper.getId());
+
+            // Check if it's already there
+            if (!grantedProtocolMapperEntities.contains(grantedProtocolMapperEntity)) {
+                em.persist(grantedProtocolMapperEntity);
+                em.flush();
+                grantedProtocolMapperEntities.add(grantedProtocolMapperEntity);
+            } else {
+                mappersToRemove.remove(grantedProtocolMapperEntity);
+            }
+        }
+        // Those mappers were no longer on consentModel and will be removed
+        for (UserConsentProtocolMapperEntity toRemove : mappersToRemove) {
+            grantedProtocolMapperEntities.remove(toRemove);
+            em.remove(toRemove);
+        }
+
+        Collection<UserConsentRoleEntity> grantedRoleEntities = consentEntity.getGrantedRoles();
+        Set<UserConsentRoleEntity> rolesToRemove = new HashSet<UserConsentRoleEntity>(grantedRoleEntities);
+        for (RoleModel role : consentModel.getGrantedRoles()) {
+            UserConsentRoleEntity consentRoleEntity = new UserConsentRoleEntity();
+            consentRoleEntity.setUserConsent(consentEntity);
+            consentRoleEntity.setRoleId(role.getId());
+
+            // Check if it's already there
+            if (!grantedRoleEntities.contains(consentRoleEntity)) {
+                em.persist(consentRoleEntity);
+                em.flush();
+                grantedRoleEntities.add(consentRoleEntity);
+            } else {
+                rolesToRemove.remove(consentRoleEntity);
+            }
+        }
+        // Those roles were no longer on consentModel and will be removed
+        for (UserConsentRoleEntity toRemove : rolesToRemove) {
+            grantedRoleEntities.remove(toRemove);
+            em.remove(toRemove);
+        }
+
+        em.flush();
+    }
+
+
+    @Override
     public void grantToAllUsers(RealmModel realm, RoleModel role) {
         int num = em.createNamedQuery("grantRoleToAllUsers")
                 .setParameter("realmId", realm.getId())
@@ -337,7 +503,7 @@ public class JpaUserProvider implements UserProvider {
     }
 
     @Override
-    public UserModel getUserByServiceAccountClient(ClientModel client) {
+    public UserModel getServiceAccount(ClientModel client) {
         TypedQuery<UserEntity> query = em.createNamedQuery("getRealmUserByServiceAccount", UserEntity.class);
         query.setParameter("realmId", client.getRealm().getId());
         query.setParameter("clientInternalId", client.getId());
@@ -367,6 +533,16 @@ public class JpaUserProvider implements UserProvider {
     }
 
     @Override
+    public List<UserModel> getUsers(RealmModel realm) {
+        return getUsers(realm, false);
+    }
+
+    @Override
+    public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults) {
+        return getUsers(realm, firstResult, maxResults, false);
+    }
+
+    @Override
     public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts) {
         String queryName = includeServiceAccounts ? "getAllUsersByRealm" : "getAllUsersByRealmExcludeServiceAccount" ;
 
@@ -542,4 +718,9 @@ public class JpaUserProvider implements UserProvider {
         // Not supported yet
         return null;
     }
+
+    @Override
+    public void preRemove(RealmModel realm, StorageProviderModel link) {
+
+    }
 }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 8de1395..40112cd 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -43,6 +43,8 @@ import org.keycloak.models.UserFederationProviderCreationEventImpl;
 import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.jpa.entities.*;
 import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.storage.StorageProvider;
+import org.keycloak.storage.StorageProviderModel;
 
 import javax.persistence.EntityManager;
 import javax.persistence.TypedQuery;
@@ -812,6 +814,15 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
         em.flush();
     }
 
+
+    private void removeFederationMappersForProvider(String federationProviderId) {
+        Set<UserFederationMapperEntity> mappers = getUserFederationMapperEntitiesByFederationProvider(federationProviderId);
+        for (UserFederationMapperEntity mapper : mappers) {
+            realm.getUserFederationMappers().remove(mapper);
+            em.remove(mapper);
+        }
+    }
+
     @Override
     public List<UserFederationProviderModel> getUserFederationProviders() {
         List<UserFederationProviderEntity> entities = realm.getUserFederationProviders();
@@ -882,15 +893,6 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
             }
         }
     }
-
-    private void removeFederationMappersForProvider(String federationProviderId) {
-        Set<UserFederationMapperEntity> mappers = getUserFederationMapperEntitiesByFederationProvider(federationProviderId);
-        for (UserFederationMapperEntity mapper : mappers) {
-            realm.getUserFederationMappers().remove(mapper);
-            em.remove(mapper);
-        }
-    }
-
     @Override
     public void updateUserFederationProvider(UserFederationProviderModel model) {
         KeycloakModelUtils.ensureUniqueDisplayName(model.getDisplayName(), model, getUserFederationProviders());
@@ -1002,6 +1004,186 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
     }
 
     @Override
+    public StorageProviderModel getStorageProvider(String id) {
+        StorageProviderEntity entity = em.find(StorageProviderEntity.class, id);
+        if (entity == null) return null;
+        return toModel(entity);
+    }
+
+    @Override
+    public List<StorageProviderModel> getStorageProviders() {
+        List<StorageProviderEntity> entities = realm.getStorageProviders();
+        if (entities.isEmpty()) return Collections.EMPTY_LIST;
+        List<StorageProviderEntity> copy = new LinkedList<>();
+        for (StorageProviderEntity entity : entities) {
+            copy.add(entity);
+
+        }
+        Collections.sort(copy, new Comparator<StorageProviderEntity>() {
+
+            @Override
+            public int compare(StorageProviderEntity o1, StorageProviderEntity o2) {
+                return o1.getPriority() - o2.getPriority();
+            }
+
+        });
+        List<StorageProviderModel> result = new LinkedList<>();
+        for (StorageProviderEntity entity : copy) {
+            result.add(toModel(entity));
+        }
+
+        return Collections.unmodifiableList(result);
+    }
+
+    protected StorageProviderModel toModel(StorageProviderEntity entity) {
+        StorageProviderModel model = new StorageProviderModel();
+        model.setId(entity.getId());
+        model.setProviderName(entity.getProviderName());
+        model.getConfig().putAll(entity.getConfig());
+        model.setPriority(entity.getPriority());
+        model.setDisplayName(entity.getDisplayName());
+        return model;
+    }
+
+    @Override
+    public StorageProviderModel addStorageProvider(StorageProviderModel model) {
+        KeycloakModelUtils.ensureUniqueDisplayName(model.getDisplayName(), null, getStorageProviders());
+
+        String id = KeycloakModelUtils.generateId();
+        StorageProviderEntity entity = new StorageProviderEntity();
+        entity.setId(id);
+        entity.setRealm(realm);
+        entity.setProviderName(model.getProviderName());
+        entity.setConfig(model.getConfig());
+        entity.setPriority(model.getPriority());
+        String displayName = model.getDisplayName();
+        if (model.getDisplayName() == null) {
+            displayName = id;
+        }
+        entity.setDisplayName(displayName);
+        em.persist(entity);
+        realm.getStorageProviders().add(entity);
+        em.flush();
+        StorageProviderModel providerModel = toModel(entity);
+
+        return providerModel;
+    }
+
+    @Override
+    public void removeStorageProvider(StorageProviderModel provider) {
+        Iterator<StorageProviderEntity> it = realm.getStorageProviders().iterator();
+        while (it.hasNext()) {
+            StorageProviderEntity entity = it.next();
+            if (entity.getId().equals(provider.getId())) {
+
+                session.users().preRemove(this, provider);
+
+                it.remove();
+                em.remove(entity);
+                return;
+            }
+        }
+    }
+    @Override
+    public void updateStorageProvider(StorageProviderModel model) {
+        KeycloakModelUtils.ensureUniqueDisplayName(model.getDisplayName(), model, getStorageProviders());
+
+        Iterator<StorageProviderEntity> it = realm.getStorageProviders().iterator();
+        while (it.hasNext()) {
+            StorageProviderEntity entity = it.next();
+            if (entity.getId().equals(model.getId())) {
+                String displayName = model.getDisplayName();
+                if (displayName != null) {
+                    entity.setDisplayName(model.getDisplayName());
+                }
+                entity.setConfig(model.getConfig());
+                entity.setPriority(model.getPriority());
+                entity.setProviderName(model.getProviderName());
+                entity.setPriority(model.getPriority());
+                break;
+            }
+        }
+    }
+
+    @Override
+    public void setStorageProviders(List<StorageProviderModel> providers) {
+        for (StorageProviderModel currentProvider : providers) {
+            KeycloakModelUtils.ensureUniqueDisplayName(currentProvider.getDisplayName(), currentProvider, providers);
+        }
+
+        Iterator<StorageProviderEntity> it = realm.getStorageProviders().iterator();
+        while (it.hasNext()) {
+            StorageProviderEntity entity = it.next();
+            boolean found = false;
+            for (StorageProviderModel model : providers) {
+                if (entity.getId().equals(model.getId())) {
+                    entity.setConfig(model.getConfig());
+                    entity.setPriority(model.getPriority());
+                    entity.setProviderName(model.getProviderName());
+                    String displayName = model.getDisplayName();
+                    if (displayName != null) {
+                        entity.setDisplayName(displayName);
+                    }
+                    found = true;
+                    break;
+                }
+
+            }
+            if (found) continue;
+            session.users().preRemove(this, toModel(entity));
+            removeFederationMappersForProvider(entity.getId());
+
+            it.remove();
+            em.remove(entity);
+        }
+
+        List<StorageProviderModel> add = new LinkedList<>();
+        for (StorageProviderModel model : providers) {
+            boolean found = false;
+            for (StorageProviderEntity entity : realm.getStorageProviders()) {
+                if (entity.getId().equals(model.getId())) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) add.add(model);
+        }
+
+        for (StorageProviderModel model : add) {
+            StorageProviderEntity entity = new StorageProviderEntity();
+            if (model.getId() != null) {
+                entity.setId(model.getId());
+            } else {
+                String id = KeycloakModelUtils.generateId();
+                entity.setId(id);
+                model.setId(id);
+            }
+            entity.setConfig(model.getConfig());
+            entity.setPriority(model.getPriority());
+            entity.setProviderName(model.getProviderName());
+            entity.setPriority(model.getPriority());
+            String displayName = model.getDisplayName();
+            if (displayName == null) {
+                displayName = entity.getId();
+            }
+            entity.setDisplayName(displayName);
+            entity.setRealm(realm);
+            em.persist(entity);
+            realm.getStorageProviders().add(entity);
+
+        }
+    }
+
+    protected StorageProviderEntity getStorageProviderEntityById(String id) {
+        for (StorageProviderEntity entity : realm.getStorageProviders()) {
+            if (entity.getId().equals(id)) {
+                return entity;
+            }
+        }
+        return null;
+    }
+
+    @Override
     public RoleModel getRole(String name) {
         return session.realms().getRealmRole(this, name);
     }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
index 9cce404..5d51fc9 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
@@ -668,163 +668,6 @@ public class UserAdapter implements UserModel, JpaModel<UserEntity> {
         user.setServiceAccountClientLink(clientInternalId);
     }
 
-    @Override
-    public void addConsent(UserConsentModel consent) {
-        String clientId = consent.getClient().getId();
-
-        UserConsentEntity consentEntity = getGrantedConsentEntity(clientId);
-        if (consentEntity != null) {
-            throw new ModelDuplicateException("Consent already exists for client [" + clientId + "] and user [" + user.getId() + "]");
-        }
-
-        consentEntity = new UserConsentEntity();
-        consentEntity.setId(KeycloakModelUtils.generateId());
-        consentEntity.setUser(user);
-        consentEntity.setClientId(clientId);
-        em.persist(consentEntity);
-        em.flush();
-
-        updateGrantedConsentEntity(consentEntity, consent);
-    }
-
-    @Override
-    public UserConsentModel getConsentByClient(String clientId) {
-        UserConsentEntity entity = getGrantedConsentEntity(clientId);
-        return toConsentModel(entity);
-    }
-
-    @Override
-    public List<UserConsentModel> getConsents() {
-        TypedQuery<UserConsentEntity> query = em.createNamedQuery("userConsentsByUser", UserConsentEntity.class);
-        query.setParameter("userId", getId());
-        List<UserConsentEntity> results = query.getResultList();
-
-        List<UserConsentModel> consents = new ArrayList<UserConsentModel>();
-        for (UserConsentEntity entity : results) {
-            UserConsentModel model = toConsentModel(entity);
-            consents.add(model);
-        }
-        return consents;
-    }
-
-    @Override
-    public void updateConsent(UserConsentModel consent) {
-        String clientId = consent.getClient().getId();
-
-        UserConsentEntity consentEntity = getGrantedConsentEntity(clientId);
-        if (consentEntity == null) {
-            throw new ModelException("Consent not found for client [" + clientId + "] and user [" + user.getId() + "]");
-        }
-
-        updateGrantedConsentEntity(consentEntity, consent);
-    }
-
-    @Override
-    public boolean revokeConsentForClient(String clientId) {
-        UserConsentEntity consentEntity = getGrantedConsentEntity(clientId);
-        if (consentEntity == null) return false;
-
-        em.remove(consentEntity);
-        em.flush();
-        return true;
-    }
-
-
-    private UserConsentEntity getGrantedConsentEntity(String clientId) {
-        TypedQuery<UserConsentEntity> query = em.createNamedQuery("userConsentByUserAndClient", UserConsentEntity.class);
-        query.setParameter("userId", getId());
-        query.setParameter("clientId", clientId);
-        List<UserConsentEntity> results = query.getResultList();
-        if (results.size() > 1) {
-            throw new ModelException("More results found for user [" + getUsername() + "] and client [" + clientId + "]");
-        } else if (results.size() == 1) {
-            return results.get(0);
-        } else {
-            return null;
-        }
-    }
-
-    private UserConsentModel toConsentModel(UserConsentEntity entity) {
-        if (entity == null) {
-            return null;
-        }
-
-        ClientModel client = realm.getClientById(entity.getClientId());
-        if (client == null) {
-            throw new ModelException("Client with id " + entity.getClientId() + " is not available");
-        }
-        UserConsentModel model = new UserConsentModel(client);
-
-        Collection<UserConsentRoleEntity> grantedRoleEntities = entity.getGrantedRoles();
-        if (grantedRoleEntities != null) {
-            for (UserConsentRoleEntity grantedRole : grantedRoleEntities) {
-                RoleModel grantedRoleModel = realm.getRoleById(grantedRole.getRoleId());
-                if (grantedRoleModel != null) {
-                    model.addGrantedRole(grantedRoleModel);
-                }
-            }
-        }
-
-        Collection<UserConsentProtocolMapperEntity> grantedProtocolMapperEntities = entity.getGrantedProtocolMappers();
-        if (grantedProtocolMapperEntities != null) {
-            for (UserConsentProtocolMapperEntity grantedProtMapper : grantedProtocolMapperEntities) {
-                ProtocolMapperModel protocolMapper = client.getProtocolMapperById(grantedProtMapper.getProtocolMapperId());
-                model.addGrantedProtocolMapper(protocolMapper );
-            }
-        }
-
-        return model;
-    }
-
-    // Update roles and protocolMappers to given consentEntity from the consentModel
-    private void updateGrantedConsentEntity(UserConsentEntity consentEntity, UserConsentModel consentModel) {
-        Collection<UserConsentProtocolMapperEntity> grantedProtocolMapperEntities = consentEntity.getGrantedProtocolMappers();
-        Collection<UserConsentProtocolMapperEntity> mappersToRemove = new HashSet<UserConsentProtocolMapperEntity>(grantedProtocolMapperEntities);
-
-        for (ProtocolMapperModel protocolMapper : consentModel.getGrantedProtocolMappers()) {
-            UserConsentProtocolMapperEntity grantedProtocolMapperEntity = new UserConsentProtocolMapperEntity();
-            grantedProtocolMapperEntity.setUserConsent(consentEntity);
-            grantedProtocolMapperEntity.setProtocolMapperId(protocolMapper.getId());
-
-            // Check if it's already there
-            if (!grantedProtocolMapperEntities.contains(grantedProtocolMapperEntity)) {
-                em.persist(grantedProtocolMapperEntity);
-                em.flush();
-                grantedProtocolMapperEntities.add(grantedProtocolMapperEntity);
-            } else {
-                mappersToRemove.remove(grantedProtocolMapperEntity);
-            }
-        }
-        // Those mappers were no longer on consentModel and will be removed
-        for (UserConsentProtocolMapperEntity toRemove : mappersToRemove) {
-            grantedProtocolMapperEntities.remove(toRemove);
-            em.remove(toRemove);
-        }
-
-        Collection<UserConsentRoleEntity> grantedRoleEntities = consentEntity.getGrantedRoles();
-        Set<UserConsentRoleEntity> rolesToRemove = new HashSet<UserConsentRoleEntity>(grantedRoleEntities);
-        for (RoleModel role : consentModel.getGrantedRoles()) {
-            UserConsentRoleEntity consentRoleEntity = new UserConsentRoleEntity();
-            consentRoleEntity.setUserConsent(consentEntity);
-            consentRoleEntity.setRoleId(role.getId());
-
-            // Check if it's already there
-            if (!grantedRoleEntities.contains(consentRoleEntity)) {
-                em.persist(consentRoleEntity);
-                em.flush();
-                grantedRoleEntities.add(consentRoleEntity);
-            } else {
-                rolesToRemove.remove(consentRoleEntity);
-            }
-        }
-        // Those roles were no longer on consentModel and will be removed
-        for (UserConsentRoleEntity toRemove : rolesToRemove) {
-            grantedRoleEntities.remove(toRemove);
-            em.remove(toRemove);
-        }
-
-        em.flush();
-    }
 
     @Override
     public boolean equals(Object o) {
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
new file mode 100755
index 0000000..a6b05cd
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/BrokerLinkEntity.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.storage.jpa.entity;
+
+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;
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@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 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"),
+        @NamedQuery(name= "deleteBrokerLinkByRealmAndLink", query="delete from BrokerLinkEntity social where social.userId IN (select u.id from UserEntity u where realmId=:realmId and u.federationLink=:link)"),
+        @NamedQuery(name= "deleteBrokerLinkByUser", query="delete from BrokerLinkEntity social where social.userId = :userId and social.realmId = :realmId")
+})
+@Table(name="BROKER_LINK")
+@Entity
+@IdClass(BrokerLinkEntity.Key.class)
+public class BrokerLinkEntity {
+
+    @Id
+    @Column(name = "USER_ID")
+    private String userId;
+
+    @Id
+    @Column(name = "IDENTITY_PROVIDER")
+    protected String identityProvider;
+
+    @Column(name = "REALM_ID")
+    protected String realmId;
+
+    @Column(name = "STORAGE_PROVIDER_ID")
+    protected String storageProviderId;
+
+    @Column(name = "BROKER_USER_ID")
+    protected String brokerUserId;
+    @Column(name = "BROKER_USERNAME")
+    protected String brokerUserName;
+
+    @Column(name = "TOKEN")
+    protected String token;
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public String getIdentityProvider() {
+        return identityProvider;
+    }
+
+    public void setIdentityProvider(String identityProvider) {
+        this.identityProvider = identityProvider;
+    }
+
+    public String getBrokerUserId() {
+        return brokerUserId;
+    }
+
+    public void setBrokerUserId(String brokerUserId) {
+        this.brokerUserId = brokerUserId;
+    }
+
+    public String getBrokerUserName() {
+        return brokerUserName;
+    }
+
+    public void setBrokerUserName(String brokerUserName) {
+        this.brokerUserName = brokerUserName;
+    }
+
+    public String getStorageProviderId() {
+        return storageProviderId;
+    }
+
+    public void setStorageProviderId(String storageProviderId) {
+        this.storageProviderId = storageProviderId;
+    }
+
+    public String getRealmId() {
+        return realmId;
+    }
+
+    public void setRealmId(String realmId) {
+        this.realmId = realmId;
+    }
+
+    public void setToken(String token) {
+        this.token = token;
+    }
+
+    public String getToken() {
+        return token;
+    }
+
+    public static class Key implements Serializable {
+
+        protected String userId;
+
+        protected String identityProvider;
+
+        public Key() {
+        }
+
+        public Key(String userId, String identityProvider) {
+            this.userId = userId;
+            this.identityProvider = identityProvider;
+        }
+
+        public String getUserId() {
+            return userId;
+        }
+
+        public String getIdentityProvider() {
+            return identityProvider;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            Key key = (Key) o;
+
+            if (identityProvider != null ? !identityProvider.equals(key.identityProvider) : key.identityProvider != null)
+                return false;
+            if (userId != null ? !userId.equals(key.userId != null ? key.userId : null) : key.userId != null) return false;
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = userId != null ? userId.hashCode() : 0;
+            result = 31 * result + (identityProvider != null ? identityProvider.hashCode() : 0);
+            return result;
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null) return false;
+        if (!(o instanceof BrokerLinkEntity)) return false;
+
+        BrokerLinkEntity key = (BrokerLinkEntity) o;
+
+        if (identityProvider != null ? !identityProvider.equals(key.identityProvider) : key.identityProvider != null)
+            return false;
+        if (userId != null ? !userId.equals(key.userId != null ? key.userId : null) : key.userId != null) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = userId != null ? userId.hashCode() : 0;
+        result = 31 * result + (identityProvider != null ? identityProvider.hashCode() : 0);
+        return result;
+    }
+
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserAttributeEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserAttributeEntity.java
new file mode 100755
index 0000000..071eacd
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserAttributeEntity.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.storage.jpa.entity;
+
+import javax.persistence.Access;
+import javax.persistence.AccessType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@NamedQueries({
+        @NamedQuery(name="getFederatedAttributesByNameAndValue", query="select attr from FederatedUserAttributeEntity attr where attr.name = :name and attr.value = :value and attr.realmId=:realmId"),
+        @NamedQuery(name="getFederatedAttributesByUser", query="select attr from FederatedUserAttributeEntity attr where attr.userId = :userId and attr.realmId=:realmId"),
+        @NamedQuery(name="deleteUserFederatedAttributesByUser", query="delete from  FederatedUserAttributeEntity attr where attr.userId = :userId and attr.realmId=:realmId"),
+        @NamedQuery(name="deleteUserFederatedAttributesByUserAndName", query="delete from  FederatedUserAttributeEntity attr where attr.userId = :userId and attr.name=:name and attr.realmId=:realmId"),
+        @NamedQuery(name="deleteUserFederatedAttributesByRealm", query="delete from  FederatedUserAttributeEntity attr where attr.realmId=:realmId"),
+        @NamedQuery(name="deleteFederatedAttributesByStorageProvider", query="delete from FederatedUserAttributeEntity e where e.storageProviderId=:storageProviderId"),
+        @NamedQuery(name="deleteUserFederatedAttributesByRealmAndLink", query="delete from  FederatedUserAttributeEntity attr where attr.userId IN (select u.id from UserEntity u where u.realmId=:realmId and u.federationLink=:link)")
+})
+@Table(name="FED_USER_ATTRIBUTE")
+@Entity
+public class FederatedUserAttributeEntity {
+
+    @Id
+    @Column(name="ID", length = 36)
+    @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity.  This avoids an extra SQL
+    protected String id;
+
+    @Column(name = "USER_ID")
+    protected String userId;
+
+    @Column(name = "REALM_ID")
+    protected String realmId;
+
+    @Column(name = "STORAGE_PROVIDER_ID")
+    protected String storageProviderId;
+
+    @Column(name = "NAME")
+    protected String name;
+    @Column(name = "VALUE")
+    protected String value;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public String getRealmId() {
+        return realmId;
+    }
+
+    public void setRealmId(String realmId) {
+        this.realmId = realmId;
+    }
+
+    public String getStorageProviderId() {
+        return storageProviderId;
+    }
+
+    public void setStorageProviderId(String storageProviderId) {
+        this.storageProviderId = storageProviderId;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null) return false;
+        if (!(o instanceof FederatedUserAttributeEntity)) return false;
+
+        FederatedUserAttributeEntity that = (FederatedUserAttributeEntity) o;
+
+        if (!id.equals(that.getId())) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return id.hashCode();
+    }
+
+
+}
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
new file mode 100755
index 0000000..8066310
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentEntity.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.storage.jpa.entity;
+
+import javax.persistence.Access;
+import javax.persistence.AccessType;
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@Entity
+@Table(name="FED_USER_CONSENT", uniqueConstraints = {
+        @UniqueConstraint(columnNames = {"USER_ID", "CLIENT_ID"})
+})
+@NamedQueries({
+        @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 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 {
+
+    @Id
+    @Column(name="ID", length = 36)
+    @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity.  This avoids an extra SQL
+    protected String id;
+
+    @Column(name = "USER_ID")
+    protected String userId;
+
+    @Column(name = "REALM_ID")
+    protected String realmId;
+
+    @Column(name = "STORAGE_PROVIDER_ID")
+    protected String storageProviderId;
+
+    @Column(name="CLIENT_ID")
+    protected String clientId;
+
+    @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "userConsent")
+    Collection<FederatedUserConsentRoleEntity> grantedRoles = new ArrayList<FederatedUserConsentRoleEntity>();
+
+    @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "userConsent")
+    Collection<FederatedUserConsentProtocolMapperEntity> grantedProtocolMappers = new ArrayList<FederatedUserConsentProtocolMapperEntity>();
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public String getRealmId() {
+        return realmId;
+    }
+
+    public void setRealmId(String realmId) {
+        this.realmId = realmId;
+    }
+
+    public String getStorageProviderId() {
+        return storageProviderId;
+    }
+
+    public void setStorageProviderId(String storageProviderId) {
+        this.storageProviderId = storageProviderId;
+    }
+
+    public String getClientId() {
+        return clientId;
+    }
+
+    public void setClientId(String clientId) {
+        this.clientId = clientId;
+    }
+
+    public Collection<FederatedUserConsentRoleEntity> getGrantedRoles() {
+        return grantedRoles;
+    }
+
+    public void setGrantedRoles(Collection<FederatedUserConsentRoleEntity> grantedRoles) {
+        this.grantedRoles = grantedRoles;
+    }
+
+    public Collection<FederatedUserConsentProtocolMapperEntity> getGrantedProtocolMappers() {
+        return grantedProtocolMappers;
+    }
+
+    public void setGrantedProtocolMappers(Collection<FederatedUserConsentProtocolMapperEntity> grantedProtocolMappers) {
+        this.grantedProtocolMappers = grantedProtocolMappers;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null) return false;
+        if (!(o instanceof FederatedUserConsentEntity)) return false;
+
+        FederatedUserConsentEntity that = (FederatedUserConsentEntity) o;
+
+        if (!id.equals(that.getId())) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return id.hashCode();
+    }
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentProtocolMapperEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentProtocolMapperEntity.java
new file mode 100755
index 0000000..f7da2cc
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentProtocolMapperEntity.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.storage.jpa.entity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@NamedQueries({
+        @NamedQuery(name="deleteFederatedUserConsentProtMappersByRealm", query=
+                "delete from FederatedUserConsentProtocolMapperEntity csm where csm.userConsent IN (select consent from FederatedUserConsentEntity consent where consent.realmId = :realmId)"),
+        @NamedQuery(name="deleteFederatedUserConsentProtMappersByUser", query="delete from FederatedUserConsentProtocolMapperEntity csm where csm.userConsent IN (select consent from FederatedUserConsentEntity consent where consent.userId = :userId and consent.realmId = :realmId)"),
+        @NamedQuery(name="deleteFederatedUserConsentProtMappersByStorageProvider", query="delete from FederatedUserConsentProtocolMapperEntity csm where csm.userConsent IN (select consent from FederatedUserConsentEntity consent where consent.storageProviderId = :storageProviderId)"),
+        @NamedQuery(name="deleteFederatedUserConsentProtMappersByProtocolMapper", query="delete from FederatedUserConsentProtocolMapperEntity csm where csm.protocolMapperId = :protocolMapperId"),
+        @NamedQuery(name="deleteFederatedUserConsentProtMappersByClient", query="delete from FederatedUserConsentProtocolMapperEntity csm where csm.userConsent IN (select consent from FederatedUserConsentEntity consent where consent.clientId = :clientId)"),
+})
+@Entity
+@Table(name="FED_USER_CONSENT_PROT_MAPPER")
+@IdClass(FederatedUserConsentProtocolMapperEntity.Key.class)
+public class FederatedUserConsentProtocolMapperEntity {
+
+    @Id
+    @ManyToOne(fetch= FetchType.LAZY)
+    @JoinColumn(name = "USER_CONSENT_ID")
+    protected FederatedUserConsentEntity userConsent;
+
+    @Id
+    @Column(name="PROTOCOL_MAPPER_ID")
+    protected String protocolMapperId;
+
+    public FederatedUserConsentEntity getUserConsent() {
+        return userConsent;
+    }
+
+    public void setUserConsent(FederatedUserConsentEntity userConsent) {
+        this.userConsent = userConsent;
+    }
+
+    public String getProtocolMapperId() {
+        return protocolMapperId;
+    }
+
+    public void setProtocolMapperId(String protocolMapperId) {
+        this.protocolMapperId = protocolMapperId;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null) return false;
+        if (!(o instanceof FederatedUserConsentProtocolMapperEntity)) return false;
+
+        FederatedUserConsentProtocolMapperEntity that = (FederatedUserConsentProtocolMapperEntity)o;
+        Key myKey = new Key(this.userConsent, this.protocolMapperId);
+        Key hisKey = new Key(that.userConsent, that.protocolMapperId);
+        return myKey.equals(hisKey);
+    }
+
+    @Override
+    public int hashCode() {
+        Key myKey = new Key(this.userConsent, this.protocolMapperId);
+        return myKey.hashCode();
+    }
+
+    public static class Key implements Serializable {
+
+        protected FederatedUserConsentEntity userConsent;
+
+        protected String protocolMapperId;
+
+        public Key() {
+        }
+
+        public Key(FederatedUserConsentEntity userConsent, String protocolMapperId) {
+            this.userConsent = userConsent;
+            this.protocolMapperId = protocolMapperId;
+        }
+
+        public FederatedUserConsentEntity getUserConsent() {
+            return userConsent;
+        }
+
+        public String getProtocolMapperId() {
+            return protocolMapperId;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            Key key = (Key) o;
+
+            if (userConsent != null ? !userConsent.getId().equals(key.userConsent != null ? key.userConsent.getId() : null) : key.userConsent != null) return false;
+            if (protocolMapperId != null ? !protocolMapperId.equals(key.protocolMapperId) : key.protocolMapperId != null) return false;
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = userConsent != null ? userConsent.getId().hashCode() : 0;
+            result = 31 * result + (protocolMapperId != null ? protocolMapperId.hashCode() : 0);
+            return result;
+        }
+    }
+
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentRoleEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentRoleEntity.java
new file mode 100755
index 0000000..d74865d
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentRoleEntity.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.storage.jpa.entity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@NamedQueries({
+        @NamedQuery(name="deleteFederatedUserConsentRolesByRealm", query="delete from FederatedUserConsentRoleEntity grantedRole where grantedRole.userConsent IN (select consent from FederatedUserConsentEntity consent where consent.realmId = :realmId)"),
+        @NamedQuery(name="deleteFederatedUserConsentRolesByUser", query="delete from FederatedUserConsentRoleEntity grantedRole where grantedRole.userConsent IN (select consent from FederatedUserConsentEntity consent where consent.userId = :userId and consent.realmId = :realmId)"),
+        @NamedQuery(name="deleteFederatedUserConsentRolesByStorageProvider", query="delete from FederatedUserConsentRoleEntity grantedRole where grantedRole.userConsent IN (select consent from FederatedUserConsentEntity consent where consent.storageProviderId = :storageProviderId)"),
+        @NamedQuery(name="deleteFederatedUserConsentRolesByRole", query="delete from FederatedUserConsentRoleEntity grantedRole where grantedRole.roleId = :roleId"),
+        @NamedQuery(name="deleteFederatedUserConsentRolesByClient", query="delete from FederatedUserConsentRoleEntity grantedRole where grantedRole.userConsent IN (select consent from FederatedUserConsentEntity consent where consent.clientId = :clientId)"),
+})
+@Entity
+@Table(name="FED_USER_CONSENT_ROLE")
+@IdClass(FederatedUserConsentRoleEntity.Key.class)
+public class FederatedUserConsentRoleEntity {
+
+    @Id
+    @ManyToOne(fetch= FetchType.LAZY)
+    @JoinColumn(name = "USER_CONSENT_ID")
+    protected FederatedUserConsentEntity userConsent;
+
+    @Id
+    @Column(name="ROLE_ID")
+    protected String roleId;
+
+    public FederatedUserConsentEntity getUserConsent() {
+        return userConsent;
+    }
+
+    public void setUserConsent(FederatedUserConsentEntity userConsent) {
+        this.userConsent = userConsent;
+    }
+
+    public String getRoleId() {
+        return roleId;
+    }
+
+    public void setRoleId(String roleId) {
+        this.roleId = roleId;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null) return false;
+        if (!(o instanceof FederatedUserConsentRoleEntity)) return false;
+
+        FederatedUserConsentRoleEntity that = (FederatedUserConsentRoleEntity)o;
+        Key myKey = new Key(this.userConsent, this.roleId);
+        Key hisKey = new Key(that.userConsent, that.roleId);
+        return myKey.equals(hisKey);
+    }
+
+    @Override
+    public int hashCode() {
+        Key myKey = new Key(this.userConsent, this.roleId);
+        return myKey.hashCode();
+    }
+
+    public static class Key implements Serializable {
+
+        protected FederatedUserConsentEntity userConsent;
+
+        protected String roleId;
+
+        public Key() {
+        }
+
+        public Key(FederatedUserConsentEntity userConsent, String roleId) {
+            this.userConsent = userConsent;
+            this.roleId = roleId;
+        }
+
+        public FederatedUserConsentEntity getUserConsent() {
+            return userConsent;
+        }
+
+        public String getRoleId() {
+            return roleId;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            Key key = (Key) o;
+
+            if (userConsent != null ? !userConsent.getId().equals(key.userConsent != null ? key.userConsent.getId() : null) : key.userConsent != null) return false;
+            if (roleId != null ? !roleId.equals(key.roleId) : key.roleId != null) return false;
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = userConsent != null ? userConsent.getId().hashCode() : 0;
+            result = 31 * result + (roleId != null ? roleId.hashCode() : 0);
+            return result;
+        }
+    }
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialEntity.java
new file mode 100755
index 0000000..996608b
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialEntity.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.storage.jpa.entity;
+
+import org.keycloak.models.jpa.entities.UserEntity;
+
+import javax.persistence.Access;
+import javax.persistence.AccessType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@NamedQueries({
+        @NamedQuery(name="federatedUserCredentialByUser", query="select cred from FederatedUserCredentialEntity cred where cred.userId = :userId"),
+        @NamedQuery(name="federatedUserCredentialByUserAndType", query="select cred from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.type = :type"),
+        @NamedQuery(name="deleteFederatedUserCredentialByUser", query="delete from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.realmId = :realmId"),
+        @NamedQuery(name="deleteFederatedUserCredentialByUserAndType", query="delete from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.type = :type"),
+        @NamedQuery(name="deleteFederatedUserCredentialByUserAndTypeAndDevice", query="delete from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.type = :type and cred.device = :device"),
+        @NamedQuery(name="deleteFederatedUserCredentialsByRealm", query="delete from FederatedUserCredentialEntity cred where cred.realmId=:realmId"),
+        @NamedQuery(name="deleteFederatedUserCredentialsByStorageProvider", query="delete from FederatedUserCredentialEntity cred where cred.storageProviderId=:storageProviderId"),
+        @NamedQuery(name="deleteFederatedUserCredentialsByRealmAndLink", query="delete from FederatedUserCredentialEntity cred where cred.userId IN (select u.id from UserEntity u where u.realmId=:realmId and u.federationLink=:link)")
+
+})
+@Table(name="FED_USER_CREDENTIAL")
+@Entity
+public class FederatedUserCredentialEntity {
+    @Id
+    @Column(name="ID", length = 36)
+    @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity.  This avoids an extra SQL
+    protected String id;
+
+    @Column(name="TYPE")
+    protected String type;
+    @Column(name="VALUE")
+    protected String value;
+    @Column(name="DEVICE")
+    protected String device;
+    @Column(name="SALT")
+    protected byte[] salt;
+    @Column(name="HASH_ITERATIONS")
+    protected int hashIterations;
+    @Column(name="CREATED_DATE")
+    protected Long createdDate;
+    
+    @Column(name="USER_ID")
+    protected String userId;
+
+    @Column(name = "REALM_ID")
+    protected String realmId;
+
+    @Column(name = "STORAGE_PROVIDER_ID")
+    protected String storageProviderId;
+
+
+
+    @Column(name="COUNTER")
+    protected int counter;
+
+    @Column(name="ALGORITHM")
+    protected String algorithm;
+    @Column(name="DIGITS")
+    protected int digits;
+    @Column(name="PERIOD")
+    protected int period;
+
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getDevice() {
+        return device;
+    }
+
+    public void setDevice(String device) {
+        this.device = device;
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public String getRealmId() {
+        return realmId;
+    }
+
+    public void setRealmId(String realmId) {
+        this.realmId = realmId;
+    }
+
+    public String getStorageProviderId() {
+        return storageProviderId;
+    }
+
+    public void setStorageProviderId(String storageProviderId) {
+        this.storageProviderId = storageProviderId;
+    }
+
+    public byte[] getSalt() {
+        return salt;
+    }
+
+    public void setSalt(byte[] salt) {
+        this.salt = salt;
+    }
+
+    public int getHashIterations() {
+        return hashIterations;
+    }
+
+    public void setHashIterations(int hashIterations) {
+        this.hashIterations = hashIterations;
+    }
+
+    public Long getCreatedDate() {
+        return createdDate;
+    }
+
+    public void setCreatedDate(Long createdDate) {
+        this.createdDate = createdDate;
+    }
+
+    public int getCounter() {
+        return counter;
+    }
+
+    public void setCounter(int counter) {
+        this.counter = counter;
+    }
+
+    public String getAlgorithm() {
+        return algorithm;
+    }
+
+    public void setAlgorithm(String algorithm) {
+        this.algorithm = algorithm;
+    }
+
+    public int getDigits() {
+        return digits;
+    }
+
+    public void setDigits(int digits) {
+        this.digits = digits;
+    }
+
+    public int getPeriod() {
+        return period;
+    }
+
+    public void setPeriod(int period) {
+        this.period = period;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null) return false;
+        if (!(o instanceof FederatedUserCredentialEntity)) return false;
+
+        FederatedUserCredentialEntity that = (FederatedUserCredentialEntity) o;
+
+        if (!id.equals(that.getId())) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return id.hashCode();
+    }
+
+}
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
new file mode 100755
index 0000000..ecfaa49
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserGroupMembershipEntity.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.storage.jpa.entity;
+
+import org.keycloak.models.jpa.entities.UserEntity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@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.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"),
+        @NamedQuery(name="deleteFederatedUserGroupMembershipsByRealmAndLink", query="delete from  FederatedUserGroupMembershipEntity mapping where mapping.userId IN (select u.id from UserEntity u where u.realmId=:realmId and u.federationLink=:link)"),
+        @NamedQuery(name="deleteFederatedUserGroupMembershipsByGroup", query="delete from FederatedUserGroupMembershipEntity m where m.groupId = :groupId"),
+        @NamedQuery(name="deleteFederatedUserGroupMembershipsByUser", query="delete from FederatedUserGroupMembershipEntity m where m.userId = :userId and m.realmId = :realmId")
+
+})
+@Table(name="FED_USER_GROUP_MEMBERSHIP")
+@Entity
+@IdClass(FederatedUserGroupMembershipEntity.Key.class)
+public class FederatedUserGroupMembershipEntity {
+
+    @Id
+    @Column(name = "USER_ID")
+    protected String userId;
+
+    @Id
+    @Column(name = "GROUP_ID")
+    protected String groupId;
+
+    @Column(name = "REALM_ID")
+    protected String realmId;
+
+    @Column(name = "STORAGE_PROVIDER_ID")
+    protected String storageProviderId;
+
+    public String getGroupId() {
+        return groupId;
+    }
+
+    public void setGroupId(String groupId) {
+        this.groupId = groupId;
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public String getRealmId() {
+        return realmId;
+    }
+
+    public void setRealmId(String realmId) {
+        this.realmId = realmId;
+    }
+
+    public String getStorageProviderId() {
+        return storageProviderId;
+    }
+
+    public void setStorageProviderId(String storageProviderId) {
+        this.storageProviderId = storageProviderId;
+    }
+
+    public static class Key implements Serializable {
+
+        protected String userId;
+
+        protected String groupId;
+
+        public Key() {
+        }
+
+        public Key(String userId, String groupId) {
+            this.userId = userId;
+            this.groupId = groupId;
+        }
+
+        public String getUserId() {
+            return userId;
+        }
+
+        public String getGroupId() {
+            return groupId;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            Key key = (Key) o;
+
+            if (!groupId.equals(key.groupId)) return false;
+            if (!userId.equals(key.userId)) return false;
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = userId.hashCode();
+            result = 31 * result + groupId.hashCode();
+            return result;
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null) return false;
+        if (!(o instanceof FederatedUserGroupMembershipEntity)) return false;
+
+        FederatedUserGroupMembershipEntity key = (FederatedUserGroupMembershipEntity) o;
+
+        if (!groupId.equals(key.groupId)) return false;
+        if (!userId.equals(key.userId)) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = userId.hashCode();
+        result = 31 * result + groupId.hashCode();
+        return result;
+    }
+
+}
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
new file mode 100755
index 0000000..f555628
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRequiredActionEntity.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.storage.jpa.entity;
+
+import org.keycloak.models.jpa.entities.UserEntity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@NamedQueries({
+        @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)")
+})
+@Entity
+@Table(name="FED_USER_REQUIRED_ACTION")
+@IdClass(FederatedUserRequiredActionEntity.Key.class)
+public class FederatedUserRequiredActionEntity {
+
+    @Id
+    @Column(name="USER_ID")
+    protected String userId;
+
+    @Id
+    @Column(name="REQUIRED_ACTION")
+    protected String action;
+
+    @Column(name = "REALM_ID")
+    protected String realmId;
+
+    @Column(name = "STORAGE_PROVIDER_ID")
+    protected String storageProviderId;
+
+    public String getAction() {
+        return action;
+    }
+
+    public void setAction(String action) {
+        this.action = action;
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public String getRealmId() {
+        return realmId;
+    }
+
+    public void setRealmId(String realmId) {
+        this.realmId = realmId;
+    }
+
+    public String getStorageProviderId() {
+        return storageProviderId;
+    }
+
+    public void setStorageProviderId(String storageProviderId) {
+        this.storageProviderId = storageProviderId;
+    }
+
+    public static class Key implements Serializable {
+
+        protected String userId;
+
+        protected String action;
+
+        public Key() {
+        }
+
+        public Key(String user, String action) {
+            this.userId = user;
+            this.action = action;
+        }
+
+        public String getUserId() {
+            return userId;
+        }
+
+        public String getAction() {
+            return action;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            Key key = (Key) o;
+
+            if (action != key.action) return false;
+            if (userId != null ? !userId.equals(key.userId != null ? key.userId : null) : key.userId != null) return false;
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = userId != null ? userId.hashCode() : 0;
+            result = 31 * result + (action != null ? action.hashCode() : 0);
+            return result;
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null) return false;
+        if (!(o instanceof FederatedUserRequiredActionEntity)) return false;
+
+        FederatedUserRequiredActionEntity key = (FederatedUserRequiredActionEntity) o;
+
+        if (action != key.action) return false;
+        if (userId != null ? !userId.equals(key.userId != null ? key.userId : null) : key.userId != null) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = userId != null ? userId.hashCode() : 0;
+        result = 31 * result + (action != null ? action.hashCode() : 0);
+        return result;
+    }
+
+
+}
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
new file mode 100755
index 0000000..719fce6
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRoleMappingEntity.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.storage.jpa.entity;
+
+import org.keycloak.models.jpa.entities.UserEntity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@NamedQueries({
+        @NamedQuery(name="feduserHasRole", query="select m from FederatedUserRoleMappingEntity m where m.userId = :userId and m.roleId = :roleId"),
+        @NamedQuery(name="feduserRoleMappings", query="select m from FederatedUserRoleMappingEntity m where m.userId = :userId"),
+        @NamedQuery(name="deleteFederatedUserRoleMappingsByRealm", query="delete from  FederatedUserRoleMappingEntity mapping where mapping.realmId=:realmId"),
+        @NamedQuery(name="deleteFederatedUserRoleMappingsByStorageProvider", query="delete from FederatedUserRoleMappingEntity e where e.storageProviderId=:storageProviderId"),
+        @NamedQuery(name="deleteFederatedUserRoleMappingsByRealmAndLink", query="delete from  FederatedUserRoleMappingEntity mapping where mapping.userId IN (select u.id from UserEntity u where u.realmId=:realmId and u.federationLink=:link)"),
+        @NamedQuery(name="deleteFederatedUserRoleMappingsByRole", query="delete from FederatedUserRoleMappingEntity m where m.roleId = :roleId"),
+        @NamedQuery(name="deleteFederatedUserRoleMappingsByUser", query="delete from FederatedUserRoleMappingEntity m where m.userId = :userId and m.realmId = :realmId"),
+
+})
+@Table(name="FED_USER_ROLE_MAPPING")
+@Entity
+@IdClass(FederatedUserRoleMappingEntity.Key.class)
+public class FederatedUserRoleMappingEntity {
+
+    @Id
+    @Column(name = "USER_ID")
+    protected String userId;
+
+    @Id
+    @Column(name = "ROLE_ID")
+    protected String roleId;
+
+    @Column(name = "REALM_ID")
+    protected String realmId;
+
+    @Column(name = "STORAGE_PROVIDER_ID")
+    protected String storageProviderId;
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public String getRealmId() {
+        return realmId;
+    }
+
+    public void setRealmId(String realmId) {
+        this.realmId = realmId;
+    }
+
+    public String getStorageProviderId() {
+        return storageProviderId;
+    }
+
+    public void setStorageProviderId(String storageProviderId) {
+        this.storageProviderId = storageProviderId;
+    }
+
+    public String getRoleId() {
+        return roleId;
+    }
+
+    public void setRoleId(String roleId) {
+        this.roleId = roleId;
+    }
+
+
+    public static class Key implements Serializable {
+
+        protected String userId;
+
+        protected String roleId;
+
+        public Key() {
+        }
+
+        public Key(String userId, String roleId) {
+            this.userId = userId;
+            this.roleId = roleId;
+        }
+
+        public String getUserId() {
+            return userId;
+        }
+
+        public String getRoleId() {
+            return roleId;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            Key key = (Key) o;
+
+            if (!roleId.equals(key.roleId)) return false;
+            if (!userId.equals(key.userId)) return false;
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = userId.hashCode();
+            result = 31 * result + roleId.hashCode();
+            return result;
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null) return false;
+        if (!(o instanceof FederatedUserRoleMappingEntity)) return false;
+
+        FederatedUserRoleMappingEntity key = (FederatedUserRoleMappingEntity) o;
+
+        if (!roleId.equals(key.roleId)) return false;
+        if (!userId.equals(key.userId)) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = userId.hashCode();
+        result = 31 * result + roleId.hashCode();
+        return result;
+    }
+
+}
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
new file mode 100644
index 0000000..4ec58bd
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java
@@ -0,0 +1,734 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.jpa;
+
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.FederatedIdentityModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserConsentModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.jpa.entities.CredentialEntity;
+import org.keycloak.models.utils.FederatedCredentials;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.storage.StorageId;
+import org.keycloak.storage.StorageProviderModel;
+import org.keycloak.storage.federated.UserAttributeFederatedStorage;
+import org.keycloak.storage.federated.UserBrokerLinkFederatedStorage;
+import org.keycloak.storage.federated.UserConsentFederatedStorage;
+import org.keycloak.storage.federated.UserCredentialsFederatedStorage;
+import org.keycloak.storage.federated.UserFederatedStorageProvider;
+import org.keycloak.storage.federated.UserGroupMembershipFederatedStorage;
+import org.keycloak.storage.federated.UserRequiredActionsFederatedStorage;
+import org.keycloak.storage.federated.UserRoleMappingsFederatedStorage;
+import org.keycloak.storage.jpa.entity.BrokerLinkEntity;
+import org.keycloak.storage.jpa.entity.FederatedUserAttributeEntity;
+import org.keycloak.storage.jpa.entity.FederatedUserConsentEntity;
+import org.keycloak.storage.jpa.entity.FederatedUserConsentProtocolMapperEntity;
+import org.keycloak.storage.jpa.entity.FederatedUserConsentRoleEntity;
+import org.keycloak.storage.jpa.entity.FederatedUserCredentialEntity;
+import org.keycloak.storage.jpa.entity.FederatedUserGroupMembershipEntity;
+import org.keycloak.storage.jpa.entity.FederatedUserRequiredActionEntity;
+import org.keycloak.storage.jpa.entity.FederatedUserRoleMappingEntity;
+
+import javax.persistence.EntityManager;
+import javax.persistence.TypedQuery;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class JpaUserFederatedStorageProvider implements
+        UserFederatedStorageProvider,
+        UserAttributeFederatedStorage,
+        UserBrokerLinkFederatedStorage,
+        UserConsentFederatedStorage,
+        UserCredentialsFederatedStorage,
+        UserGroupMembershipFederatedStorage,
+        UserRequiredActionsFederatedStorage,
+        UserRoleMappingsFederatedStorage {
+
+    private final KeycloakSession session;
+    protected EntityManager em;
+
+    public JpaUserFederatedStorageProvider(KeycloakSession session, EntityManager em) {
+        this.session = session;
+        this.em = em;
+    }
+
+    @Override
+    public void close() {
+
+    }
+
+
+    @Override
+    public void setAttribute(RealmModel realm, UserModel user, String name, List<String> values) {
+        deleteAttribute(realm, user, name);
+        em.flush();
+        for (String value : values) {
+            persistAttributeValue(realm, user, name, value);
+        }
+    }
+
+    private void deleteAttribute(RealmModel realm, UserModel user, String name) {
+        em.createNamedQuery("deleteUserFederatedAttributesByUserAndName")
+                .setParameter("userId", user.getId())
+                .setParameter("realmId", realm.getId())
+                .setParameter("name", name)
+                .executeUpdate();
+    }
+
+    private void persistAttributeValue(RealmModel realm, UserModel user, String name, String value) {
+        FederatedUserAttributeEntity attr = new FederatedUserAttributeEntity();
+        attr.setId(KeycloakModelUtils.generateId());
+        attr.setName(name);
+        attr.setValue(value);
+        attr.setUserId(user.getId());
+        attr.setRealmId(realm.getId());
+        attr.setStorageProviderId(StorageId.resolveProviderId(user));
+        em.persist(attr);
+    }
+
+    @Override
+    public void setSingleAttribute(RealmModel realm, UserModel user, String name, String value) {
+        deleteAttribute(realm, user, name);
+        em.flush();
+        persistAttributeValue(realm, user, name, value);
+    }
+
+    @Override
+    public void removeAttribute(RealmModel realm, UserModel user, String name) {
+        deleteAttribute(realm, user, name);
+        em.flush();
+    }
+
+    @Override
+    public MultivaluedHashMap<String, String> getAttributes(RealmModel realm, UserModel user) {
+        TypedQuery<FederatedUserAttributeEntity> query = em.createNamedQuery("getFederatedAttributesByUser", FederatedUserAttributeEntity.class);
+        List<FederatedUserAttributeEntity> list = query
+                .setParameter("userId", user.getId())
+                .setParameter("realmId", realm.getId())
+                .getResultList();
+        MultivaluedHashMap<String, String> result = new MultivaluedHashMap<>();
+        for (FederatedUserAttributeEntity entity : list) {
+            result.add(entity.getName(), entity.getValue());
+
+        }
+        return result;
+    }
+
+    @Override
+    public String getUserByFederatedIdentity(FederatedIdentityModel link, RealmModel realm) {
+        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();
+        if (results.isEmpty()) {
+            return null;
+        } else if (results.size() > 1) {
+            throw new IllegalStateException("More results found for identityProvider=" + link.getIdentityProvider() +
+                    ", userId=" + link.getUserId() + ", results=" + results);
+        } else {
+            return results.get(0);
+        }
+    }
+
+    @Override
+    public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel link) {
+        BrokerLinkEntity entity = new BrokerLinkEntity();
+        entity.setRealmId(realm.getId());
+        entity.setUserId(user.getId());
+        entity.setBrokerUserId(link.getUserId());
+        entity.setIdentityProvider(link.getIdentityProvider());
+        entity.setToken(link.getToken());
+        entity.setBrokerUserName(link.getUserName());
+        entity.setStorageProviderId(StorageId.resolveProviderId(user));
+        em.persist(entity);
+
+    }
+
+    @Override
+    public boolean removeFederatedIdentity(RealmModel realm, UserModel user, String socialProvider) {
+        BrokerLinkEntity entity = getBrokerLinkEntity(realm, user, socialProvider);
+        if (entity == null) return false;
+        em.remove(entity);
+        return true;
+    }
+
+    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;
+    }
+
+    @Override
+    public void updateFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel model) {
+        BrokerLinkEntity entity = getBrokerLinkEntity(realm, user, model.getIdentityProvider());
+        if (entity == null) return;
+        entity.setBrokerUserName(model.getUserName());
+        entity.setBrokerUserId(model.getUserId());
+        entity.setToken(model.getToken());
+        em.persist(entity);
+        em.flush();
+
+    }
+
+    @Override
+    public Set<FederatedIdentityModel> getFederatedIdentities(UserModel user, RealmModel realm) {
+        TypedQuery<BrokerLinkEntity> query = em.createNamedQuery("findBrokerLinkByUser", BrokerLinkEntity.class)
+                .setParameter("userId", user.getId());
+        List<BrokerLinkEntity> results = query.getResultList();
+        Set<FederatedIdentityModel> set = new HashSet<>();
+        for (BrokerLinkEntity entity : results) {
+            FederatedIdentityModel model = new FederatedIdentityModel(entity.getIdentityProvider(), entity.getBrokerUserId(), entity.getBrokerUserName(), entity.getToken());
+            set.add(model);
+        }
+        return set;
+    }
+
+    @Override
+    public FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm) {
+        BrokerLinkEntity entity = getBrokerLinkEntity(realm, user, socialProvider);
+        if (entity == null) return null;
+        return new FederatedIdentityModel(entity.getIdentityProvider(), entity.getBrokerUserId(), entity.getBrokerUserName(), entity.getToken());
+    }
+
+    @Override
+    public void addConsent(RealmModel realm, UserModel user, UserConsentModel consent) {
+        String clientId = consent.getClient().getId();
+
+        FederatedUserConsentEntity consentEntity = getGrantedConsentEntity(user, clientId);
+        if (consentEntity != null) {
+            throw new ModelDuplicateException("Consent already exists for client [" + clientId + "] and user [" + user.getId() + "]");
+        }
+
+        consentEntity = new FederatedUserConsentEntity();
+        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();
+
+        updateGrantedConsentEntity(consentEntity, consent);
+
+    }
+
+    @Override
+    public UserConsentModel getConsentByClient(RealmModel realm, UserModel user, String clientInternalId) {
+        FederatedUserConsentEntity entity = getGrantedConsentEntity(user, clientInternalId);
+        return toConsentModel(realm, entity);
+    }
+
+    @Override
+    public List<UserConsentModel> getConsents(RealmModel realm, UserModel user) {
+        TypedQuery<FederatedUserConsentEntity> query = em.createNamedQuery("userFederatedConsentsByUser", FederatedUserConsentEntity.class);
+        query.setParameter("userId", user.getId());
+        List<FederatedUserConsentEntity> results = query.getResultList();
+
+        List<UserConsentModel> consents = new ArrayList<UserConsentModel>();
+        for (FederatedUserConsentEntity entity : results) {
+            UserConsentModel model = toConsentModel(realm, entity);
+            consents.add(model);
+        }
+        return consents;
+    }
+
+    @Override
+    public void updateConsent(RealmModel realm, UserModel user, UserConsentModel consent) {
+        String clientId = consent.getClient().getId();
+
+        FederatedUserConsentEntity consentEntity = getGrantedConsentEntity(user, clientId);
+        if (consentEntity == null) {
+            throw new ModelException("Consent not found for client [" + clientId + "] and user [" + user.getId() + "]");
+        }
+
+        updateGrantedConsentEntity(consentEntity, consent);
+
+    }
+
+    @Override
+    public boolean revokeConsentForClient(RealmModel realm, UserModel user, String clientInternalId) {
+        FederatedUserConsentEntity consentEntity = getGrantedConsentEntity(user, clientInternalId);
+        if (consentEntity == null) return false;
+
+        em.remove(consentEntity);
+        em.flush();
+        return true;
+    }
+
+    private FederatedUserConsentEntity getGrantedConsentEntity(UserModel user, String clientId) {
+        TypedQuery<FederatedUserConsentEntity> query = em.createNamedQuery("userFederatedConsentByUserAndClient", FederatedUserConsentEntity.class);
+        query.setParameter("userId", user.getId());
+        query.setParameter("clientId", clientId);
+        List<FederatedUserConsentEntity> results = query.getResultList();
+        if (results.size() > 1) {
+            throw new ModelException("More results found for user [" + user.getUsername() + "] and client [" + clientId + "]");
+        } else if (results.size() == 1) {
+            return results.get(0);
+        } else {
+            return null;
+        }
+    }
+
+
+    private UserConsentModel toConsentModel(RealmModel realm, FederatedUserConsentEntity entity) {
+        if (entity == null) {
+            return null;
+        }
+
+        ClientModel client = realm.getClientById(entity.getClientId());
+        if (client == null) {
+            throw new ModelException("Client with id " + entity.getClientId() + " is not available");
+        }
+        UserConsentModel model = new UserConsentModel(client);
+
+        Collection<FederatedUserConsentRoleEntity> grantedRoleEntities = entity.getGrantedRoles();
+        if (grantedRoleEntities != null) {
+            for (FederatedUserConsentRoleEntity grantedRole : grantedRoleEntities) {
+                RoleModel grantedRoleModel = realm.getRoleById(grantedRole.getRoleId());
+                if (grantedRoleModel != null) {
+                    model.addGrantedRole(grantedRoleModel);
+                }
+            }
+        }
+
+        Collection<FederatedUserConsentProtocolMapperEntity> grantedProtocolMapperEntities = entity.getGrantedProtocolMappers();
+        if (grantedProtocolMapperEntities != null) {
+            for (FederatedUserConsentProtocolMapperEntity grantedProtMapper : grantedProtocolMapperEntities) {
+                ProtocolMapperModel protocolMapper = client.getProtocolMapperById(grantedProtMapper.getProtocolMapperId());
+                model.addGrantedProtocolMapper(protocolMapper);
+            }
+        }
+
+        return model;
+    }
+
+    // Update roles and protocolMappers to given consentEntity from the consentModel
+    private void updateGrantedConsentEntity(FederatedUserConsentEntity consentEntity, UserConsentModel consentModel) {
+        Collection<FederatedUserConsentProtocolMapperEntity> grantedProtocolMapperEntities = consentEntity.getGrantedProtocolMappers();
+        Collection<FederatedUserConsentProtocolMapperEntity> mappersToRemove = new HashSet<>(grantedProtocolMapperEntities);
+
+        for (ProtocolMapperModel protocolMapper : consentModel.getGrantedProtocolMappers()) {
+            FederatedUserConsentProtocolMapperEntity grantedProtocolMapperEntity = new FederatedUserConsentProtocolMapperEntity();
+            grantedProtocolMapperEntity.setUserConsent(consentEntity);
+            grantedProtocolMapperEntity.setProtocolMapperId(protocolMapper.getId());
+
+            // Check if it's already there
+            if (!grantedProtocolMapperEntities.contains(grantedProtocolMapperEntity)) {
+                em.persist(grantedProtocolMapperEntity);
+                em.flush();
+                grantedProtocolMapperEntities.add(grantedProtocolMapperEntity);
+            } else {
+                mappersToRemove.remove(grantedProtocolMapperEntity);
+            }
+        }
+        // Those mappers were no longer on consentModel and will be removed
+        for (FederatedUserConsentProtocolMapperEntity toRemove : mappersToRemove) {
+            grantedProtocolMapperEntities.remove(toRemove);
+            em.remove(toRemove);
+        }
+
+        Collection<FederatedUserConsentRoleEntity> grantedRoleEntities = consentEntity.getGrantedRoles();
+        Set<FederatedUserConsentRoleEntity> rolesToRemove = new HashSet<>(grantedRoleEntities);
+        for (RoleModel role : consentModel.getGrantedRoles()) {
+            FederatedUserConsentRoleEntity consentRoleEntity = new FederatedUserConsentRoleEntity();
+            consentRoleEntity.setUserConsent(consentEntity);
+            consentRoleEntity.setRoleId(role.getId());
+
+            // Check if it's already there
+            if (!grantedRoleEntities.contains(consentRoleEntity)) {
+                em.persist(consentRoleEntity);
+                em.flush();
+                grantedRoleEntities.add(consentRoleEntity);
+            } else {
+                rolesToRemove.remove(consentRoleEntity);
+            }
+        }
+        // Those roles were no longer on consentModel and will be removed
+        for (FederatedUserConsentRoleEntity toRemove : rolesToRemove) {
+            grantedRoleEntities.remove(toRemove);
+            em.remove(toRemove);
+        }
+
+        em.flush();
+    }
+
+
+
+    @Override
+    public List<UserCredentialValueModel> getCredentials(RealmModel realm, UserModel user) {
+        TypedQuery<FederatedUserCredentialEntity> query = em.createNamedQuery("federatedUserCredentialByUser", FederatedUserCredentialEntity.class)
+                .setParameter("userId", user.getId());
+        List<FederatedUserCredentialEntity> results = query.getResultList();
+        List<UserCredentialValueModel> list = new LinkedList<>();
+        for (FederatedUserCredentialEntity credEntity : results) {
+            UserCredentialValueModel credModel = new UserCredentialValueModel();
+            credModel.setId(credEntity.getId());
+            credModel.setType(credEntity.getType());
+            credModel.setDevice(credEntity.getDevice());
+            credModel.setValue(credEntity.getValue());
+            credModel.setCreatedDate(credEntity.getCreatedDate());
+            credModel.setSalt(credEntity.getSalt());
+            credModel.setHashIterations(credEntity.getHashIterations());
+            credModel.setCounter(credEntity.getCounter());
+            credModel.setAlgorithm(credEntity.getAlgorithm());
+            credModel.setDigits(credEntity.getDigits());
+            credModel.setPeriod(credEntity.getPeriod());
+
+            list.add(credModel);
+        }
+        return list;
+    }
+
+    @Override
+    public void updateCredential(RealmModel realm, UserModel user, UserCredentialModel cred) {
+        FederatedCredentials.updateCredential(session, this, realm, user, cred);
+
+    }
+
+    @Override
+    public void updateCredential(RealmModel realm, UserModel user, UserCredentialValueModel cred) {
+        FederatedUserCredentialEntity entity = null;
+        if (cred.getId() != null) entity = em.find(FederatedUserCredentialEntity.class, cred.getId());
+        boolean newEntity = false;
+        if (entity == null) {
+            entity = new FederatedUserCredentialEntity();
+            entity.setId(KeycloakModelUtils.generateId());
+            newEntity = true;
+        }
+        entity.setUserId(user.getId());
+        entity.setRealmId(realm.getId());
+        entity.setStorageProviderId(StorageId.resolveProviderId(user));
+        entity.setAlgorithm(cred.getAlgorithm());
+        entity.setCounter(cred.getCounter());
+        Long createdDate = cred.getCreatedDate();
+        if (createdDate == null) createdDate = System.currentTimeMillis();
+        entity.setCreatedDate(createdDate);
+        entity.setDevice(cred.getDevice());
+        entity.setDigits(cred.getDigits());
+        entity.setHashIterations(cred.getHashIterations());
+        entity.setPeriod(cred.getPeriod());
+        entity.setSalt(cred.getSalt());
+        entity.setType(cred.getType());
+        entity.setValue(cred.getValue());
+        if (newEntity) {
+            em.persist(entity);
+        }
+
+    }
+
+    @Override
+    public void removeCredential(RealmModel realm, UserModel user, UserCredentialValueModel cred) {
+        FederatedUserCredentialEntity entity = em.find(FederatedUserCredentialEntity.class, cred.getId());
+        em.remove(entity);
+    }
+
+    @Override
+    public Set<GroupModel> getGroups(RealmModel realm, UserModel user) {
+        Set<GroupModel> set = new HashSet<>();
+        TypedQuery<FederatedUserGroupMembershipEntity> query = em.createNamedQuery("feduserGroupMembership", FederatedUserGroupMembershipEntity.class);
+        query.setParameter("userId", user.getId());
+        List<FederatedUserGroupMembershipEntity> results = query.getResultList();
+        if (results.size() == 0) return set;
+        for (FederatedUserGroupMembershipEntity entity : results) {
+            GroupModel group = realm.getGroupById(entity.getGroupId());
+            set.add(group);
+        }
+        return set;
+    }
+
+    @Override
+    public void joinGroup(RealmModel realm, UserModel user, GroupModel group) {
+        if (isMemberOf(realm, user, group)) return;
+        FederatedUserGroupMembershipEntity entity = new FederatedUserGroupMembershipEntity();
+        entity.setUserId(user.getId());
+        entity.setStorageProviderId(StorageId.resolveProviderId(user));
+        entity.setGroupId(group.getId());
+        entity.setRealmId(realm.getId());
+        em.persist(entity);
+
+    }
+
+    public boolean isMemberOf(RealmModel realm, UserModel user, GroupModel group) {
+        Set<GroupModel> roles = user.getGroups();
+        return KeycloakModelUtils.isMember(roles, group);
+    }
+
+
+    @Override
+    public void leaveGroup(RealmModel realm, UserModel user, GroupModel group) {
+        if (user == null || group == null) return;
+
+        TypedQuery<FederatedUserGroupMembershipEntity> query1 = em.createNamedQuery("feduserMemberOf", FederatedUserGroupMembershipEntity.class);
+        query1.setParameter("userId", user.getId());
+        query1.setParameter("groupId", group.getId());
+        TypedQuery<FederatedUserGroupMembershipEntity> query = query1;
+        List<FederatedUserGroupMembershipEntity> results = query.getResultList();
+        if (results.size() == 0) return;
+        for (FederatedUserGroupMembershipEntity entity : results) {
+            em.remove(entity);
+        }
+        em.flush();
+
+    }
+
+
+    @Override
+    public Set<String> getRequiredActions(RealmModel realm, UserModel user) {
+        Set<String> set = new HashSet<>();
+        List<FederatedUserRequiredActionEntity> values = getRequiredActionEntities(realm, user);
+        for (FederatedUserRequiredActionEntity entity : values) {
+            set.add(entity.getAction());
+        }
+
+        return set;
+
+    }
+
+    private List<FederatedUserRequiredActionEntity> getRequiredActionEntities(RealmModel realm, UserModel user) {
+        TypedQuery<FederatedUserRequiredActionEntity> query = em.createNamedQuery("getFederatedUserRequiredActionsByUser", FederatedUserRequiredActionEntity.class)
+                .setParameter("userId", user.getId())
+                .setParameter("realmId", realm.getId());
+        return query.getResultList();
+    }
+
+    @Override
+    public void addRequiredAction(RealmModel realm, UserModel user, String action) {
+        if (user.getRequiredActions().contains(action)) return;
+        FederatedUserRequiredActionEntity entity = new FederatedUserRequiredActionEntity();
+        entity.setUserId(user.getId());
+        entity.setRealmId(realm.getId());
+        entity.setStorageProviderId(StorageId.resolveProviderId(user));
+        entity.setAction(action);
+        em.persist(entity);
+
+    }
+
+    @Override
+    public void removeRequiredAction(RealmModel realm, UserModel user, String action) {
+        List<FederatedUserRequiredActionEntity> values = getRequiredActionEntities(realm, user);
+        for (FederatedUserRequiredActionEntity entity : values) {
+            if (action.equals(entity.getAction())) em.remove(entity);
+        }
+        em.flush();
+
+    }
+
+    @Override
+    public void grantRole(RealmModel realm, UserModel user, RoleModel role) {
+        if (user.hasRole(role)) return;
+        FederatedUserRoleMappingEntity entity = new FederatedUserRoleMappingEntity();
+        entity.setUserId(user.getId());
+        entity.setStorageProviderId(StorageId.resolveProviderId(user));
+        entity.setRealmId(realm.getId());
+        entity.setRoleId(role.getId());
+        em.persist(entity);
+
+    }
+
+    @Override
+    public Set<RoleModel> getRoleMappings(RealmModel realm, UserModel user) {
+        Set<RoleModel> set = new HashSet<>();
+        TypedQuery<FederatedUserRoleMappingEntity> query = em.createNamedQuery("feduserRoleMappings", FederatedUserRoleMappingEntity.class);
+        query.setParameter("userId", user.getId());
+        List<FederatedUserRoleMappingEntity> results = query.getResultList();
+        if (results.size() == 0) return set;
+        for (FederatedUserRoleMappingEntity entity : results) {
+            RoleModel role = realm.getRoleById(entity.getRoleId());
+            set.add(role);
+        }
+        return set;
+    }
+
+    @Override
+    public void deleteRoleMapping(RealmModel realm, UserModel user, RoleModel role) {
+        TypedQuery<FederatedUserRoleMappingEntity> query = em.createNamedQuery("feduserRoleMappings", FederatedUserRoleMappingEntity.class);
+        query.setParameter("userId", user.getId());
+        List<FederatedUserRoleMappingEntity> results = query.getResultList();
+        for (FederatedUserRoleMappingEntity entity : results) {
+            if (entity.getRoleId().equals(role.getId())) em.remove(entity);
+
+        }
+        em.flush();
+    }
+
+    @Override
+    public void preRemove(RealmModel realm) {
+        int num = em.createNamedQuery("deleteFederatedUserConsentRolesByRealm")
+                .setParameter("realmId", realm.getId()).executeUpdate();
+        num = em.createNamedQuery("deleteFederatedUserConsentProtMappersByRealm")
+                .setParameter("realmId", realm.getId()).executeUpdate();
+        num = em.createNamedQuery("deleteFederatedUserConsentsByRealm")
+                .setParameter("realmId", realm.getId()).executeUpdate();
+        num = em.createNamedQuery("deleteFederatedUserRoleMappingsByRealm")
+                .setParameter("realmId", realm.getId()).executeUpdate();
+        num = em.createNamedQuery("deleteFederatedUserRequiredActionsByRealm")
+                .setParameter("realmId", realm.getId()).executeUpdate();
+        num = em.createNamedQuery("deleteBrokerLinkByRealm")
+                .setParameter("realmId", realm.getId()).executeUpdate();
+        num = em.createNamedQuery("deleteFederatedUserCredentialsByRealm")
+                .setParameter("realmId", realm.getId()).executeUpdate();
+        num = em.createNamedQuery("deleteUserFederatedAttributesByRealm")
+                .setParameter("realmId", realm.getId()).executeUpdate();
+        num = em.createNamedQuery("deleteFederatedUserGroupMembershipByRealm")
+                .setParameter("realmId", realm.getId()).executeUpdate();
+    }
+
+    @Override
+    public void preRemove(RealmModel realm, UserFederationProviderModel link) {
+        int num = em.createNamedQuery("deleteFederatedUserRoleMappingsByRealmAndLink")
+                .setParameter("realmId", realm.getId())
+                .setParameter("link", link.getId())
+                .executeUpdate();
+        num = em.createNamedQuery("deleteFederatedUserRequiredActionsByRealmAndLink")
+                .setParameter("realmId", realm.getId())
+                .setParameter("link", link.getId())
+                .executeUpdate();
+        num = em.createNamedQuery("deleteBrokerLinkByRealmAndLink")
+                .setParameter("realmId", realm.getId())
+                .setParameter("link", link.getId())
+                .executeUpdate();
+        num = em.createNamedQuery("deleteFederatedUserCredentialsByRealmAndLink")
+                .setParameter("realmId", realm.getId())
+                .setParameter("link", link.getId())
+                .executeUpdate();
+        num = em.createNamedQuery("deleteUserFederatedAttributesByRealmAndLink")
+                .setParameter("realmId", realm.getId())
+                .setParameter("link", link.getId())
+                .executeUpdate();
+    }
+
+    @Override
+    public void preRemove(RealmModel realm, RoleModel role) {
+        em.createNamedQuery("deleteFederatedUserRoleMappingsByRole").setParameter("roleId", role.getId()).executeUpdate();
+        em.createNamedQuery("deleteFederatedUserRoleMappingsByRole").setParameter("roleId", role.getId()).executeUpdate();
+    }
+
+    @Override
+    public void preRemove(RealmModel realm, GroupModel group) {
+        em.createNamedQuery("deleteFederatedUserGroupMembershipsByGroup").setParameter("groupId", group.getId()).executeUpdate();
+    }
+
+    @Override
+    public void preRemove(RealmModel realm, ClientModel client) {
+        em.createNamedQuery("deleteFederatedUserConsentProtMappersByClient").setParameter("clientId", client.getId()).executeUpdate();
+        em.createNamedQuery("deleteFederatedUserConsentRolesByClient").setParameter("clientId", client.getId()).executeUpdate();
+        em.createNamedQuery("deleteFederatedUserConsentsByClient").setParameter("clientId", client.getId()).executeUpdate();
+    }
+
+    @Override
+    public void preRemove(ProtocolMapperModel protocolMapper) {
+        em.createNamedQuery("deleteFederatedUserConsentProtMappersByProtocolMapper")
+                .setParameter("protocolMapperId", protocolMapper.getId())
+                .executeUpdate();
+    }
+
+    @Override
+    public void preRemove(RealmModel realm, UserModel user) {
+        em.createNamedQuery("deleteBrokerLinkByUser")
+                .setParameter("userId", user.getId())
+                .setParameter("realmId", realm.getId())
+                .executeUpdate();
+        em.createNamedQuery("deleteUserFederatedAttributesByUser")
+                .setParameter("userId", user.getId())
+                .setParameter("realmId", realm.getId())
+                .executeUpdate();
+        em.createNamedQuery("deleteFederatedUserConsentProtMappersByUser")
+                .setParameter("userId", user.getId())
+                .setParameter("realmId", realm.getId())
+                .executeUpdate();
+        em.createNamedQuery("deleteFederatedUserConsentRolesByUser")
+                .setParameter("userId", user.getId())
+                .setParameter("realmId", realm.getId())
+                .executeUpdate();
+        em.createNamedQuery("deleteFederatedUserConsentsByUser")
+                .setParameter("userId", user.getId())
+                .setParameter("realmId", realm.getId())
+                .executeUpdate();
+        em.createNamedQuery("deleteFederatedUserCredentialByUser")
+                .setParameter("userId", user.getId())
+                .setParameter("realmId", realm.getId())
+                .executeUpdate();
+        em.createNamedQuery("deleteFederatedUserGroupMembershipsByUser")
+                .setParameter("userId", user.getId())
+                .setParameter("realmId", realm.getId())
+                .executeUpdate();
+        em.createNamedQuery("getFederatedUserRequiredActionsByUser")
+                .setParameter("userId", user.getId())
+                .setParameter("realmId", realm.getId())
+                .executeUpdate();
+        em.createNamedQuery("deleteFederatedUserRoleMappingsByUser")
+                .setParameter("userId", user.getId())
+                .setParameter("realmId", realm.getId())
+                .executeUpdate();
+
+    }
+
+    @Override
+    public void preRemove(RealmModel realm, StorageProviderModel model) {
+        em.createNamedQuery("deleteBrokerLinkByStorageProvider")
+                .setParameter("storageProviderId", model.getId())
+                .executeUpdate();
+        em.createNamedQuery("deleteFederatedAttributesByStorageProvider")
+                .setParameter("storageProviderId", model.getId())
+                .executeUpdate();
+        em.createNamedQuery("deleteFederatedUserConsentProtMappersByStorageProvider")
+                .setParameter("storageProviderId", model.getId())
+                .executeUpdate();
+        em.createNamedQuery("deleteFederatedUserRoleMappingsByStorageProvider")
+                .setParameter("storageProviderId", model.getId())
+                .executeUpdate();
+        em.createNamedQuery("deleteFederatedUserConsentsByStorageProvider")
+                .setParameter("storageProviderId", model.getId())
+                .executeUpdate();
+        em.createNamedQuery("deleteFederatedUserCredentialsByStorageProvider")
+                .setParameter("storageProviderId", model.getId())
+                .executeUpdate();
+        em.createNamedQuery("deleteFederatedUserGroupMembershipByStorageProvider")
+                .setParameter("storageProviderId", model.getId())
+                .executeUpdate();
+        em.createNamedQuery("deleteFederatedUserRequiredActionsByStorageProvider")
+                .setParameter("storageProviderId", model.getId())
+                .executeUpdate();
+        em.createNamedQuery("deleteFederatedUserRoleMappingsByStorageProvider")
+                .setParameter("storageProviderId", model.getId())
+                .executeUpdate();
+
+    }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProviderFactory.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProviderFactory.java
new file mode 100644
index 0000000..0a3218c
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProviderFactory.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.jpa;
+
+import org.keycloak.Config;
+import org.keycloak.connections.jpa.JpaConnectionProvider;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.storage.federated.UserFederatedStorageProvider;
+import org.keycloak.storage.federated.UserFederatedStorageProviderFactory;
+
+import javax.persistence.EntityManager;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class JpaUserFederatedStorageProviderFactory implements UserFederatedStorageProviderFactory {
+    @Override
+    public UserFederatedStorageProvider create(KeycloakSession session) {
+        EntityManager em = session.getProvider(JpaConnectionProvider.class).getEntityManager();
+        return new JpaUserFederatedStorageProvider(session, em);
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+
+    }
+
+    @Override
+    public void close() {
+
+    }
+
+    @Override
+    public String getId() {
+        return "jpa";
+    }
+}
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
new file mode 100755
index 0000000..79c3e9e
--- /dev/null
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.1.0.xml
@@ -0,0 +1,194 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!--
+  ~ Copyright 2016 Red Hat, Inc. and/or its affiliates
+  ~ and other contributors as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~ http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<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="bburke@redhat.com" id="2.1.0">
+
+        <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>
+            <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" />
+            </column>
+            <column name="NAME" type="VARCHAR(255)">
+                <constraints nullable="false"/>
+            </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)"/>
+            <column name="VALUE" type="VARCHAR(2024)"/>
+        </createTable>
+        <createTable tableName="FED_USER_CONSENT">
+            <column name="ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="CLIENT_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </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>
+        <createTable tableName="FED_USER_CONSENT_ROLE">
+            <column name="USER_CONSENT_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="ROLE_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+        </createTable>
+        <createTable tableName="FED_USER_CONSENT_PROT_MAPPER">
+            <column name="USER_CONSENT_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="PROTOCOL_MAPPER_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+        </createTable>
+        <createTable tableName="FED_USER_CREDENTIAL">
+            <column name="ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <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>
+        <createTable tableName="FED_USER_GROUP_MEMBERSHIP">
+            <column name="GROUP_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </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>
+        <createTable tableName="FED_USER_REQUIRED_ACTION">
+            <column name="REQUIRED_ACTION" type="VARCHAR(255)" defaultValue=" ">
+                <constraints nullable="false"/>
+            </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>
+        <createTable tableName="FED_USER_ROLE_MAPPING">
+            <column name="ROLE_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </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>
+
+        <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>
+
+
+
+
+        <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 1f26239..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
@@ -28,22 +28,28 @@ import org.keycloak.models.CredentialValidationOutput;
 import org.keycloak.models.FederatedIdentityModel;
 import org.keycloak.models.GroupModel;
 import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.ModelException;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredActionProviderModel;
 import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserConsentModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserProvider;
 import org.keycloak.models.entities.FederatedIdentityEntity;
+import org.keycloak.models.entities.UserConsentEntity;
 import org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity;
 import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
 import org.keycloak.models.utils.CredentialValidation;
+import org.keycloak.storage.StorageProviderModel;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -140,7 +146,7 @@ public class MongoUserProvider implements UserProvider {
     }
 
     @Override
-    public UserModel getUserByServiceAccountClient(ClientModel client) {
+    public UserModel getServiceAccount(ClientModel client) {
         DBObject query = new QueryBuilder()
                 .and("serviceAccountClientLink").is(client.getId())
                 .and("realmId").is(client.getRealm().getId())
@@ -157,6 +163,17 @@ public class MongoUserProvider implements UserProvider {
         return userModels;
     }
 
+    @Override
+    public List<UserModel> getUsers(RealmModel realm) {
+        return getUsers(realm, false);
+    }
+
+    @Override
+    public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults) {
+        return getUsers(realm, firstResult, maxResults, false);
+    }
+
+
 
     @Override
     public List<UserModel> getUsers(RealmModel realm, boolean includeServiceAccounts) {
@@ -192,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 + ")");
 
@@ -509,4 +527,112 @@ public class MongoUserProvider implements UserProvider {
         // Not supported yet
         return null;
     }
+
+    @Override
+    public void addConsent(RealmModel realm, UserModel user, UserConsentModel consent) {
+        String clientId = consent.getClient().getId();
+        if (getConsentEntityByClientId(user, clientId) != null) {
+            throw new ModelDuplicateException("Consent already exists for client [" + clientId + "] and user [" + user.getId() + "]");
+        }
+
+        MongoUserConsentEntity consentEntity = new MongoUserConsentEntity();
+        consentEntity.setUserId(user.getId());
+        consentEntity.setClientId(clientId);
+        fillEntityFromModel(consent, consentEntity);
+        getMongoStore().insertEntity(consentEntity, invocationContext);
+    }
+
+    @Override
+    public UserConsentModel getConsentByClient(RealmModel realm, UserModel user, String clientId) {
+        UserConsentEntity consentEntity = getConsentEntityByClientId(user, clientId);
+        return consentEntity!=null ? toConsentModel(realm, consentEntity) : null;
+    }
+
+    @Override
+    public List<UserConsentModel> getConsents(RealmModel realm, UserModel user) {
+        List<UserConsentModel> result = new ArrayList<UserConsentModel>();
+
+        DBObject query = new QueryBuilder()
+                .and("userId").is(user.getId())
+                .get();
+        List<MongoUserConsentEntity> grantedConsents = getMongoStore().loadEntities(MongoUserConsentEntity.class, query, invocationContext);
+
+        for (UserConsentEntity consentEntity : grantedConsents) {
+            UserConsentModel model = toConsentModel(realm, consentEntity);
+            result.add(model);
+        }
+
+        return result;
+    }
+
+    private MongoUserConsentEntity getConsentEntityByClientId(UserModel user, String clientId) {
+        DBObject query = new QueryBuilder()
+                .and("userId").is(user.getId())
+                .and("clientId").is(clientId)
+                .get();
+        return getMongoStore().loadSingleEntity(MongoUserConsentEntity.class, query, invocationContext);
+    }
+
+    private UserConsentModel toConsentModel(RealmModel realm, UserConsentEntity entity) {
+        ClientModel client = realm.getClientById(entity.getClientId());
+        if (client == null) {
+            throw new ModelException("Client with id " + entity.getClientId() + " is not available");
+        }
+        UserConsentModel model = new UserConsentModel(client);
+
+        for (String roleId : entity.getGrantedRoles()) {
+            RoleModel roleModel = realm.getRoleById(roleId);
+            if (roleModel != null) {
+                model.addGrantedRole(roleModel);
+            }
+        }
+
+        for (String protMapperId : entity.getGrantedProtocolMappers()) {
+            ProtocolMapperModel protocolMapper = client.getProtocolMapperById(protMapperId);
+            model.addGrantedProtocolMapper(protocolMapper);
+        }
+        return model;
+    }
+
+    // Fill roles and protocolMappers to entity
+    private void fillEntityFromModel(UserConsentModel consent, MongoUserConsentEntity consentEntity) {
+        List<String> roleIds = new LinkedList<String>();
+        for (RoleModel role : consent.getGrantedRoles()) {
+            roleIds.add(role.getId());
+        }
+        consentEntity.setGrantedRoles(roleIds);
+
+        List<String> protMapperIds = new LinkedList<String>();
+        for (ProtocolMapperModel protMapperModel : consent.getGrantedProtocolMappers()) {
+            protMapperIds.add(protMapperModel.getId());
+        }
+        consentEntity.setGrantedProtocolMappers(protMapperIds);
+    }
+
+    @Override
+    public void updateConsent(RealmModel realm, UserModel user, UserConsentModel consent) {
+        String clientId = consent.getClient().getId();
+        MongoUserConsentEntity consentEntity = getConsentEntityByClientId(user, clientId);
+        if (consentEntity == null) {
+            throw new ModelException("Consent not found for client [" + clientId + "] and user [" + user.getId() + "]");
+        } else {
+            fillEntityFromModel(consent, consentEntity);
+            getMongoStore().updateEntity(consentEntity, invocationContext);
+        }
+    }
+
+    @Override
+    public boolean revokeConsentForClient(RealmModel realm, UserModel user, String clientId) {
+        MongoUserConsentEntity entity = getConsentEntityByClientId(user, clientId);
+        if (entity == null) {
+            return false;
+        }
+
+        return getMongoStore().removeEntity(entity, invocationContext);
+    }
+
+    @Override
+    public void preRemove(RealmModel realm, StorageProviderModel link) {
+
+    }
 }
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index 2dcda48..c532cbd 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -51,6 +51,7 @@ import org.keycloak.models.entities.IdentityProviderEntity;
 import org.keycloak.models.entities.IdentityProviderMapperEntity;
 import org.keycloak.models.entities.RequiredActionProviderEntity;
 import org.keycloak.models.entities.RequiredCredentialEntity;
+import org.keycloak.models.entities.StorageProviderEntity;
 import org.keycloak.models.entities.UserFederationMapperEntity;
 import org.keycloak.models.entities.UserFederationProviderEntity;
 import org.keycloak.models.mongo.keycloak.entities.MongoClientEntity;
@@ -59,6 +60,7 @@ import org.keycloak.models.mongo.keycloak.entities.MongoGroupEntity;
 import org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity;
 import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
 import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.storage.StorageProviderModel;
 
 import java.security.Key;
 import java.security.PrivateKey;
@@ -991,6 +993,14 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
         updateRealm();
     }
 
+
+    private void removeFederationMappersForProvider(String federationProviderId) {
+        Set<UserFederationMapperEntity> mappers = getUserFederationMapperEntitiesByFederationProvider(federationProviderId);
+        for (UserFederationMapperEntity mapper : mappers) {
+            getMongoEntity().getUserFederationMappers().remove(mapper);
+        }
+    }
+
     @Override
     public UserFederationProviderModel addUserFederationProvider(String providerName, Map<String, String> config, int priority, String displayName, int fullSyncPeriod, int changedSyncPeriod, int lastSync) {
         KeycloakModelUtils.ensureUniqueDisplayName(displayName, null, getUserFederationProviders());
@@ -1032,14 +1042,6 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
         }
         updateRealm();
     }
-
-    private void removeFederationMappersForProvider(String federationProviderId) {
-        Set<UserFederationMapperEntity> mappers = getUserFederationMapperEntitiesByFederationProvider(federationProviderId);
-        for (UserFederationMapperEntity mapper : mappers) {
-            getMongoEntity().getUserFederationMappers().remove(mapper);
-        }
-    }
-
     @Override
     public void updateUserFederationProvider(UserFederationProviderModel model) {
         KeycloakModelUtils.ensureUniqueDisplayName(model.getDisplayName(), model, getUserFederationProviders());
@@ -1169,6 +1171,173 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
     }
 
     @Override
+    public StorageProviderModel addStorageProvider(StorageProviderModel model) {
+        KeycloakModelUtils.ensureUniqueDisplayName(model.getDisplayName(), null, getStorageProviders());
+
+        StorageProviderEntity entity = new StorageProviderEntity();
+        entity.setId(KeycloakModelUtils.generateId());
+        entity.setPriority(model.getPriority());
+        entity.setProviderName(model.getProviderName());
+        entity.setConfig(model.getConfig());
+        String displayName = model.getDisplayName();
+        if (displayName == null) {
+            displayName = entity.getId();
+        }
+        entity.setDisplayName(displayName);
+        realm.getStorageProviders().add(entity);
+        updateRealm();
+
+        StorageProviderModel providerModel = new StorageProviderModel(entity.getId(), model.getProviderName(),
+                model.getConfig(), model.getPriority(), displayName);
+
+
+        return providerModel;
+    }
+
+    @Override
+    public void updateStorageProvider(StorageProviderModel provider) {
+        KeycloakModelUtils.ensureUniqueDisplayName(provider.getDisplayName(), provider, getStorageProviders());
+
+        Iterator<StorageProviderEntity> it = realm.getStorageProviders().iterator();
+        while (it.hasNext()) {
+            StorageProviderEntity entity = it.next();
+            if (entity.getId().equals(provider.getId())) {
+                entity.setProviderName(provider.getProviderName());
+                entity.setConfig(provider.getConfig());
+                entity.setPriority(provider.getPriority());
+                String displayName = provider.getDisplayName();
+                if (displayName != null) {
+                    entity.setDisplayName(provider.getDisplayName());
+                }
+            }
+        }
+        updateRealm();
+
+    }
+
+    @Override
+    public void removeStorageProvider(StorageProviderModel provider) {
+        Iterator<StorageProviderEntity> it = realm.getStorageProviders().iterator();
+        while (it.hasNext()) {
+            StorageProviderEntity entity = it.next();
+            if (entity.getId().equals(provider.getId())) {
+                session.users().preRemove(this, new StorageProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig(), entity.getPriority(), entity.getDisplayName()
+                        ));
+
+                it.remove();
+            }
+        }
+        updateRealm();
+
+    }
+
+    @Override
+    public void setStorageProviders(List<StorageProviderModel> providers) {
+        for (StorageProviderModel currentProvider : providers) {
+            KeycloakModelUtils.ensureUniqueDisplayName(currentProvider.getDisplayName(), currentProvider, providers);
+        }
+
+        List<StorageProviderEntity> existingProviders = realm.getStorageProviders();
+        List<StorageProviderEntity> toRemove = new LinkedList<>();
+        for (StorageProviderEntity entity : existingProviders) {
+            boolean found = false;
+            for (StorageProviderModel model : providers) {
+                if (entity.getId().equals(model.getId())) {
+                    entity.setConfig(model.getConfig());
+                    entity.setPriority(model.getPriority());
+                    entity.setProviderName(model.getProviderName());
+                    String displayName = model.getDisplayName();
+                    if (displayName != null) {
+                        entity.setDisplayName(displayName);
+                    }
+                    found = true;
+                    break;
+                }
+
+            }
+            if (found) continue;
+            session.users().preRemove(this, new StorageProviderModel(entity.getId(), entity.getProviderName(),
+                    entity.getConfig(), entity.getPriority(), entity.getDisplayName()));
+            toRemove.add(entity);
+        }
+
+        for (StorageProviderEntity entity : toRemove) {
+            realm.getStorageProviders().remove(entity);
+        }
+
+        List<StorageProviderModel> add = new LinkedList<>();
+        for (StorageProviderModel model : providers) {
+            boolean found = false;
+            for (StorageProviderEntity entity : realm.getStorageProviders()) {
+                if (entity.getId().equals(model.getId())) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) add.add(model);
+        }
+
+        for (StorageProviderModel model : add) {
+            StorageProviderEntity entity = new StorageProviderEntity();
+            if (model.getId() != null) {
+                entity.setId(model.getId());
+            } else {
+                String id = KeycloakModelUtils.generateId();
+                entity.setId(id);
+                model.setId(id);
+            }
+            entity.setProviderName(model.getProviderName());
+            entity.setConfig(model.getConfig());
+            entity.setPriority(model.getPriority());
+            String displayName = model.getDisplayName();
+            if (displayName == null) {
+                displayName = entity.getId();
+            }
+            entity.setDisplayName(displayName);
+            realm.getStorageProviders().add(entity);
+
+        }
+
+        updateRealm();
+
+    }
+
+    @Override
+    public List<StorageProviderModel> getStorageProviders() {
+        List<StorageProviderEntity> entities = realm.getStorageProviders();
+        if (entities.isEmpty()) return Collections.EMPTY_LIST;
+        List<StorageProviderEntity> copy = new LinkedList<>();
+        for (StorageProviderEntity entity : entities) {
+            copy.add(entity);
+
+        }
+        Collections.sort(copy, new Comparator<StorageProviderEntity>() {
+
+            @Override
+            public int compare(StorageProviderEntity o1, StorageProviderEntity o2) {
+                return o1.getPriority() - o2.getPriority();
+            }
+
+        });
+        List<StorageProviderModel> result = new LinkedList<>();
+        for (StorageProviderEntity entity : copy) {
+            result.add(new StorageProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig(), entity.getPriority(), entity.getDisplayName()
+                    ));
+        }
+
+        return Collections.unmodifiableList(result);
+    }
+
+    @Override
+    public StorageProviderModel getStorageProvider(String id) {
+        for (StorageProviderEntity entity : realm.getStorageProviders()) {
+            if (entity.getId().equals(id)) return new StorageProviderModel(entity.getId(), entity.getProviderName(),
+                    entity.getConfig(), entity.getPriority(), entity.getDisplayName());
+        }
+        return null;
+    }
+
+    @Override
     public boolean isEventsEnabled() {
         return realm.isEventsEnabled();
     }
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
index 601bc4a..b89ab01 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
@@ -566,108 +566,6 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
         updateUser();
     }
 
-    @Override
-    public void addConsent(UserConsentModel consent) {
-        String clientId = consent.getClient().getId();
-        if (getConsentEntityByClientId(clientId) != null) {
-            throw new ModelDuplicateException("Consent already exists for client [" + clientId + "] and user [" + user.getId() + "]");
-        }
-
-        MongoUserConsentEntity consentEntity = new MongoUserConsentEntity();
-        consentEntity.setUserId(getId());
-        consentEntity.setClientId(clientId);
-        fillEntityFromModel(consent, consentEntity);
-        getMongoStore().insertEntity(consentEntity, invocationContext);
-    }
-
-    @Override
-    public UserConsentModel getConsentByClient(String clientId) {
-        UserConsentEntity consentEntity = getConsentEntityByClientId(clientId);
-        return consentEntity!=null ? toConsentModel(consentEntity) : null;
-    }
-
-    @Override
-    public List<UserConsentModel> getConsents() {
-        List<UserConsentModel> result = new ArrayList<UserConsentModel>();
-
-        DBObject query = new QueryBuilder()
-                .and("userId").is(getId())
-                .get();
-        List<MongoUserConsentEntity> grantedConsents = getMongoStore().loadEntities(MongoUserConsentEntity.class, query, invocationContext);
-
-        for (UserConsentEntity consentEntity : grantedConsents) {
-            UserConsentModel model = toConsentModel(consentEntity);
-            result.add(model);
-        }
-
-        return result;
-    }
-
-    private MongoUserConsentEntity getConsentEntityByClientId(String clientId) {
-        DBObject query = new QueryBuilder()
-                .and("userId").is(getId())
-                .and("clientId").is(clientId)
-                .get();
-        return getMongoStore().loadSingleEntity(MongoUserConsentEntity.class, query, invocationContext);
-    }
-
-    private UserConsentModel toConsentModel(UserConsentEntity entity) {
-        ClientModel client = realm.getClientById(entity.getClientId());
-        if (client == null) {
-            throw new ModelException("Client with id " + entity.getClientId() + " is not available");
-        }
-        UserConsentModel model = new UserConsentModel(client);
-
-        for (String roleId : entity.getGrantedRoles()) {
-            RoleModel roleModel = realm.getRoleById(roleId);
-            if (roleModel != null) {
-                model.addGrantedRole(roleModel);
-            }
-        }
-
-        for (String protMapperId : entity.getGrantedProtocolMappers()) {
-            ProtocolMapperModel protocolMapper = client.getProtocolMapperById(protMapperId);
-            model.addGrantedProtocolMapper(protocolMapper);
-        }
-        return model;
-    }
-
-    // Fill roles and protocolMappers to entity
-    private void fillEntityFromModel(UserConsentModel consent, MongoUserConsentEntity consentEntity) {
-        List<String> roleIds = new LinkedList<String>();
-        for (RoleModel role : consent.getGrantedRoles()) {
-            roleIds.add(role.getId());
-        }
-        consentEntity.setGrantedRoles(roleIds);
-
-        List<String> protMapperIds = new LinkedList<String>();
-        for (ProtocolMapperModel protMapperModel : consent.getGrantedProtocolMappers()) {
-            protMapperIds.add(protMapperModel.getId());
-        }
-        consentEntity.setGrantedProtocolMappers(protMapperIds);
-    }
-
-    @Override
-    public void updateConsent(UserConsentModel consent) {
-        String clientId = consent.getClient().getId();
-        MongoUserConsentEntity consentEntity = getConsentEntityByClientId(clientId);
-        if (consentEntity == null) {
-            throw new ModelException("Consent not found for client [" + clientId + "] and user [" + user.getId() + "]");
-        } else {
-            fillEntityFromModel(consent, consentEntity);
-            getMongoStore().updateEntity(consentEntity, invocationContext);
-        }
-    }
-
-    @Override
-    public boolean revokeConsentForClient(String clientId) {
-        MongoUserConsentEntity entity = getConsentEntityByClientId(clientId);
-        if (entity == null) {
-            return false;
-        }
-
-        return getMongoStore().removeEntity(entity, invocationContext);
-    }
 
     @Override
     public boolean equals(Object o) {
diff --git a/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapper.java b/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapper.java
index ce9c199..98fa98d 100644
--- a/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapper.java
+++ b/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapper.java
@@ -34,8 +34,8 @@ import org.keycloak.provider.Provider;
 public interface UserFederationMapper extends Provider {
 
     /**
-     * Sync data from federation storage to Keycloak. It's useful just if mapper needs some data preloaded from federation storage (For example
-     * load roles from federation provider and sync them to Keycloak database)
+     * Sync data from federated storage to Keycloak. It's useful just if mapper needs some data preloaded from federated storage (For example
+     * load roles from federated provider and sync them to Keycloak database)
      *
      * Applicable just if sync is supported (see UserFederationMapperFactory.getSyncConfig() )
      *
@@ -48,7 +48,7 @@ public interface UserFederationMapper extends Provider {
     UserFederationSyncResult syncDataFromFederationProviderToKeycloak(UserFederationMapperModel mapperModel, UserFederationProvider federationProvider, KeycloakSession session, RealmModel realm);
 
     /**
-     * Sync data from Keycloak back to federation storage
+     * Sync data from Keycloak back to federated storage
      *
      * @see UserFederationMapperFactory#getSyncConfig()
      * @param mapperModel
diff --git a/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java b/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java
index 661462c..0efcf2e 100644
--- a/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java
+++ b/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java
@@ -32,7 +32,7 @@ import org.keycloak.representations.idm.UserFederationMapperSyncConfigRepresenta
 public interface UserFederationMapperFactory extends ProviderFactory<UserFederationMapper>, ConfiguredProvider {
 
     /**
-     * Refers to providerName (type) of the federation provider, which this mapper can be used for. For example "ldap" or "kerberos"
+     * Refers to providerName (type) of the federated provider, which this mapper can be used for. For example "ldap" or "kerberos"
      *
      * @return providerName
      */
@@ -42,7 +42,7 @@ public interface UserFederationMapperFactory extends ProviderFactory<UserFederat
     String getDisplayType();
 
     /**
-     * Specifies if mapper supports sync data from federation storage to keycloak and viceversa.
+     * Specifies if mapper supports sync data from federated storage to keycloak and viceversa.
      * Also specifies messages to be displayed in admin console UI (For example "Sync roles from LDAP" etc)
      *
      * @return syncConfig representation
diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_3_0.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_3_0.java
index d83ae84..8d573c7 100755
--- a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_3_0.java
+++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_3_0.java
@@ -56,7 +56,7 @@ public class MigrateTo1_3_0 {
             if (fedProvider.getProviderName().equals(LDAPConstants.LDAP_PROVIDER)) {
                 Map<String, String> config = fedProvider.getConfig();
 
-                // Update config properties for LDAP federation provider
+                // Update config properties for LDAP federated provider
                 if (config.get(LDAPConstants.SEARCH_SCOPE) == null) {
                     config.put(LDAPConstants.SEARCH_SCOPE, String.valueOf(SearchControls.SUBTREE_SCOPE));
                 }
diff --git a/server-spi/src/main/java/org/keycloak/models/entities/RealmEntity.java b/server-spi/src/main/java/org/keycloak/models/entities/RealmEntity.java
index cc3356b..6780498 100755
--- a/server-spi/src/main/java/org/keycloak/models/entities/RealmEntity.java
+++ b/server-spi/src/main/java/org/keycloak/models/entities/RealmEntity.java
@@ -19,6 +19,7 @@ package org.keycloak.models.entities;
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
@@ -80,13 +81,14 @@ public class RealmEntity extends AbstractIdentifiableEntity {
     private String emailTheme;
 
     // We are using names of defaultRoles (not ids)
-    private List<String> defaultRoles = new ArrayList<String>();
-    private List<String> defaultGroups = new ArrayList<String>();
+    private List<String> defaultRoles = new LinkedList<String>();
+    private List<String> defaultGroups = new LinkedList<String>();
 
-    private List<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>();
-    private List<UserFederationProviderEntity> userFederationProviders = new ArrayList<UserFederationProviderEntity>();
-    private List<UserFederationMapperEntity> userFederationMappers = new ArrayList<UserFederationMapperEntity>();
-    private List<IdentityProviderEntity> identityProviders = new ArrayList<IdentityProviderEntity>();
+    private List<RequiredCredentialEntity> requiredCredentials = new LinkedList<>();
+    private List<StorageProviderEntity> storageProviders = new LinkedList<>();
+    private List<UserFederationProviderEntity> userFederationProviders = new LinkedList<UserFederationProviderEntity>();
+    private List<UserFederationMapperEntity> userFederationMappers = new LinkedList<UserFederationMapperEntity>();
+    private List<IdentityProviderEntity> identityProviders = new LinkedList<IdentityProviderEntity>();
 
     private Map<String, String> browserSecurityHeaders = new HashMap<String, String>();
     private Map<String, String> smtpConfig = new HashMap<String, String>();
@@ -682,6 +684,14 @@ public class RealmEntity extends AbstractIdentifiableEntity {
     public void setDefaultGroups(List<String> defaultGroups) {
         this.defaultGroups = defaultGroups;
     }
+
+    public List<StorageProviderEntity> getStorageProviders() {
+        return storageProviders;
+    }
+
+    public void setStorageProviders(List<StorageProviderEntity> storageProviders) {
+        this.storageProviders = storageProviders;
+    }
 }
 
 
diff --git a/server-spi/src/main/java/org/keycloak/models/entities/StorageProviderEntity.java b/server-spi/src/main/java/org/keycloak/models/entities/StorageProviderEntity.java
new file mode 100755
index 0000000..3845ba0
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/entities/StorageProviderEntity.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.entities;
+
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class StorageProviderEntity extends AbstractIdentifiableEntity {
+    protected String providerName;
+    protected Map<String, String> config;
+    protected int priority;
+    protected String displayName;
+
+
+    public String getProviderName() {
+        return providerName;
+    }
+
+    public void setProviderName(String providerName) {
+        this.providerName = providerName;
+    }
+
+    public Map<String, String> getConfig() {
+        return config;
+    }
+
+    public void setConfig(Map<String, String> config) {
+        this.config = config;
+    }
+
+    public int getPriority() {
+        return priority;
+    }
+
+    public void setPriority(int priority) {
+        this.priority = priority;
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public void setDisplayName(String displayName) {
+        this.displayName = displayName;
+    }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/FederatedIdentityModel.java b/server-spi/src/main/java/org/keycloak/models/FederatedIdentityModel.java
index dcda375..e7e4ee6 100755
--- a/server-spi/src/main/java/org/keycloak/models/FederatedIdentityModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/FederatedIdentityModel.java
@@ -57,4 +57,25 @@ public class FederatedIdentityModel {
     public void setToken(String token) {
         this.token = token;
     }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        FederatedIdentityModel that = (FederatedIdentityModel) o;
+
+        if (userId != null ? !userId.equals(that.userId) : that.userId != null) return false;
+        if (!identityProvider.equals(that.identityProvider)) return false;
+        return userName != null ? userName.equals(that.userName) : that.userName == null;
+
+    }
+
+    @Override
+    public int hashCode() {
+        int result = userId != null ? userId.hashCode() : 0;
+        result = 31 * result + identityProvider.hashCode();
+        result = 31 * result + (userName != null ? userName.hashCode() : 0);
+        return result;
+    }
 }
diff --git a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java
index 8521e30..1d71b1f 100755
--- a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java
+++ b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java
@@ -19,6 +19,8 @@ package org.keycloak.models;
 
 import org.keycloak.provider.Provider;
 import org.keycloak.scripting.ScriptingProvider;
+import org.keycloak.storage.federated.UserFederatedStorageProvider;
+import org.keycloak.storage.federated.UserFederatedStorageProviderFactory;
 
 import java.util.Set;
 
@@ -73,11 +75,16 @@ public interface KeycloakSession {
      */
     UserFederationManager users();
 
+    UserProvider userStorageManager();
+
     /**
      *  Keycloak user storage.  Non-federated, but possibly cache (if it is on) view of users.
      */
     UserProvider userStorage();
 
+    UserFederatedStorageProvider userFederatedStorage();
+    UserProvider userLocalStorage();
+
     /**
      * Keycloak scripting support.
      */
diff --git a/server-spi/src/main/java/org/keycloak/models/RealmModel.java b/server-spi/src/main/java/org/keycloak/models/RealmModel.java
index 65ab90e..88eb240 100755
--- a/server-spi/src/main/java/org/keycloak/models/RealmModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/RealmModel.java
@@ -19,6 +19,7 @@ package org.keycloak.models;
 
 import org.keycloak.common.enums.SslRequired;
 import org.keycloak.provider.ProviderEvent;
+import org.keycloak.storage.StorageProviderModel;
 
 import java.security.Key;
 import java.security.PrivateKey;
@@ -275,9 +276,16 @@ public interface RealmModel extends RoleContainerModel {
     public IdentityProviderMapperModel getIdentityProviderMapperById(String id);
     public IdentityProviderMapperModel getIdentityProviderMapperByName(String brokerAlias, String name);
 
+
+    StorageProviderModel addStorageProvider(StorageProviderModel model);
+    void updateStorageProvider(StorageProviderModel provider);
+    void removeStorageProvider(StorageProviderModel provider);
+    void setStorageProviders(List<StorageProviderModel> providers);
+    List<StorageProviderModel> getStorageProviders();
+    StorageProviderModel getStorageProvider(String id);
+
     // Should return list sorted by UserFederationProviderModel.priority
     List<UserFederationProviderModel> getUserFederationProviders();
-
     UserFederationProviderModel addUserFederationProvider(String providerName, Map<String, String> config, int priority, String displayName, int fullSyncPeriod, int changedSyncPeriod, int lastSync);
     void updateUserFederationProvider(UserFederationProviderModel provider);
     void removeUserFederationProvider(UserFederationProviderModel provider);
diff --git a/server-spi/src/main/java/org/keycloak/models/UserCredentialAuthenticationProvider.java b/server-spi/src/main/java/org/keycloak/models/UserCredentialAuthenticationProvider.java
new file mode 100644
index 0000000..a55145d
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/UserCredentialAuthenticationProvider.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.models;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserCredentialAuthenticationProvider {
+    Set<String> getSupportedCredentialAuthenticationTypes();
+    CredentialValidationOutput validCredential(KeycloakSession session, RealmModel realm, UserCredentialModel input);
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/UserCredentialValidatorProvider.java b/server-spi/src/main/java/org/keycloak/models/UserCredentialValidatorProvider.java
new file mode 100644
index 0000000..13fc015
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/UserCredentialValidatorProvider.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.models;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserCredentialValidatorProvider {
+    boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List<UserCredentialModel> input);
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/UserCredentialValueModel.java b/server-spi/src/main/java/org/keycloak/models/UserCredentialValueModel.java
index 65d0c89..5ef6071 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserCredentialValueModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserCredentialValueModel.java
@@ -25,7 +25,7 @@ import java.io.Serializable;
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
 public class UserCredentialValueModel implements Serializable {
-
+    private String id;
     private String type;
     private String value;
     private String device;
@@ -40,6 +40,14 @@ public class UserCredentialValueModel implements Serializable {
     private int period;
 
 
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
     public String getType() {
         return type;
     }
diff --git a/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java b/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
index ef8d182..08a070d 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
@@ -20,6 +20,7 @@ package org.keycloak.models;
 import org.jboss.logging.Logger;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.services.managers.UserManager;
+import org.keycloak.storage.StorageProviderModel;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -175,6 +176,38 @@ public class UserFederationManager implements UserProvider {
     }
 
     @Override
+    public void addConsent(RealmModel realm, UserModel user, UserConsentModel consent) {
+        validateUser(realm, user);
+        session.userStorage().addConsent(realm, user, consent);
+
+    }
+
+    @Override
+    public UserConsentModel getConsentByClient(RealmModel realm, UserModel user, String clientInternalId) {
+        validateUser(realm, user);
+        return session.userStorage().getConsentByClient(realm, user, clientInternalId);
+    }
+
+    @Override
+    public List<UserConsentModel> getConsents(RealmModel realm, UserModel user) {
+        validateUser(realm, user);
+        return session.userStorage().getConsents(realm, user);
+    }
+
+    @Override
+    public void updateConsent(RealmModel realm, UserModel user, UserConsentModel consent) {
+        validateUser(realm, user);
+        session.userStorage().updateConsent(realm, user, consent);
+
+    }
+
+    @Override
+    public boolean revokeConsentForClient(RealmModel realm, UserModel user, String clientInternalId) {
+        validateUser(realm, user);
+        return session.userStorage().revokeConsentForClient(realm, user, clientInternalId);
+    }
+
+    @Override
     public UserModel getUserById(String id, RealmModel realm) {
         UserModel user = session.userStorage().getUserById(id, realm);
         if (user != null) {
@@ -265,8 +298,8 @@ public class UserFederationManager implements UserProvider {
     }
 
     @Override
-    public UserModel getUserByServiceAccountClient(ClientModel client) {
-        UserModel user = session.userStorage().getUserByServiceAccountClient(client);
+    public UserModel getServiceAccount(ClientModel client) {
+        UserModel user = session.userStorage().getServiceAccount(client);
         if (user != null) {
             user = validateAndProxyUser(client.getRealm(), user);
         }
@@ -280,6 +313,16 @@ public class UserFederationManager implements UserProvider {
     }
 
     @Override
+    public List<UserModel> getUsers(RealmModel realm) {
+        return getUsers(realm, false);
+    }
+
+    @Override
+    public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults) {
+        return getUsers(realm, firstResult, maxResults, false);
+    }
+
+    @Override
     public int getUsersCount(RealmModel realm) {
         return session.userStorage().getUsersCount(realm);
     }
@@ -442,6 +485,11 @@ public class UserFederationManager implements UserProvider {
         session.userStorage().preRemove(protocolMapper);
     }
 
+    @Override
+    public void preRemove(RealmModel realm, StorageProviderModel link) {
+
+    }
+
     public void updateCredential(RealmModel realm, UserModel user, UserCredentialModel credential) {
         if (credential.getType().equals(UserCredentialModel.PASSWORD)) {
             if (realm.getPasswordPolicy() != null) {
diff --git a/server-spi/src/main/java/org/keycloak/models/UserLookupProvider.java b/server-spi/src/main/java/org/keycloak/models/UserLookupProvider.java
new file mode 100644
index 0000000..3597f1e
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/UserLookupProvider.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.models;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserLookupProvider {
+    UserModel getUserById(String id, RealmModel realm);
+
+    UserModel getUserByUsername(String username, RealmModel realm);
+
+    UserModel getUserByEmail(String email, RealmModel realm);
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/UserModel.java b/server-spi/src/main/java/org/keycloak/models/UserModel.java
index 013ff9c..28c35ab 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserModel.java
@@ -129,12 +129,6 @@ public interface UserModel extends RoleMapperModel {
     String getServiceAccountClientLink();
     void setServiceAccountClientLink(String clientInternalId);
 
-    void addConsent(UserConsentModel consent);
-    UserConsentModel getConsentByClient(String clientInternalId);
-    List<UserConsentModel> getConsents();
-    void updateConsent(UserConsentModel consent);
-    boolean revokeConsentForClient(String clientInternalId);
-
     public static enum RequiredAction {
         VERIFY_EMAIL, UPDATE_PROFILE, CONFIGURE_TOTP, UPDATE_PASSWORD
     }
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 67c887f..d903799 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserProvider.java
@@ -18,56 +18,40 @@
 package org.keycloak.models;
 
 import org.keycloak.provider.Provider;
+import org.keycloak.storage.StorageProviderModel;
 
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
-public interface UserProvider extends Provider {
+public interface UserProvider extends Provider, UserLookupProvider, UserQueryProvider, UserCredentialValidatorProvider, UserUpdateProvider {
     // Note: The reason there are so many query methods here is for layering a cache on top of an persistent KeycloakSession
 
-    UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions);
-    UserModel addUser(RealmModel realm, String username);
-    boolean removeUser(RealmModel realm, UserModel user);
-
     public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel socialLink);
     public boolean removeFederatedIdentity(RealmModel realm, UserModel user, String socialProvider);
     void updateFederatedIdentity(RealmModel realm, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel);
+    Set<FederatedIdentityModel> getFederatedIdentities(UserModel user, RealmModel realm);
+    FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm);
+    UserModel getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm);
 
-    UserModel getUserById(String id, RealmModel realm);
-    UserModel getUserByUsername(String username, RealmModel realm);
-    UserModel getUserByEmail(String email, RealmModel realm);
+    void addConsent(RealmModel realm, UserModel user, UserConsentModel consent);
+    UserConsentModel getConsentByClient(RealmModel realm, UserModel user, String clientInternalId);
+    List<UserConsentModel> getConsents(RealmModel realm, UserModel user);
+    void updateConsent(RealmModel realm, UserModel user, UserConsentModel consent);
+    boolean revokeConsentForClient(RealmModel realm, UserModel user, String clientInternalId);
 
-    List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults);
 
-    UserModel getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm);
-    UserModel getUserByServiceAccountClient(ClientModel client);
+    UserModel getServiceAccount(ClientModel client);
     List<UserModel> getUsers(RealmModel realm, boolean includeServiceAccounts);
-
-    // Service account is included for counts
-    int getUsersCount(RealmModel realm);
-    List<UserModel> getGroupMembers(RealmModel realm, GroupModel group);
     List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts);
-    List<UserModel> searchForUser(String search, RealmModel realm);
-    List<UserModel> searchForUser(String search, RealmModel realm, int firstResult, int maxResults);
-    List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm);
-    List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults);
-
-    // Searching by UserModel.attribute (not property)
-    List<UserModel> searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm);
-
-    Set<FederatedIdentityModel> getFederatedIdentities(UserModel user, RealmModel realm);
-    FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm);
-
-    void grantToAllUsers(RealmModel realm, RoleModel role);
 
     void preRemove(RealmModel realm);
 
     void preRemove(RealmModel realm, UserFederationProviderModel link);
+    void preRemove(RealmModel realm, StorageProviderModel link);
 
     void preRemove(RealmModel realm, RoleModel role);
     void preRemove(RealmModel realm, GroupModel group);
@@ -75,9 +59,10 @@ public interface UserProvider extends Provider {
     void preRemove(RealmModel realm, ClientModel client);
     void preRemove(ProtocolMapperModel protocolMapper);
 
-    boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List<UserCredentialModel> input);
+
     boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel... input);
     CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input);
 
+
     void close();
 }
diff --git a/server-spi/src/main/java/org/keycloak/models/UserQueryProvider.java b/server-spi/src/main/java/org/keycloak/models/UserQueryProvider.java
new file mode 100644
index 0000000..57a69ca
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/UserQueryProvider.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.models;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserQueryProvider {
+
+    // Service account is included for counts
+    int getUsersCount(RealmModel realm);
+
+    List<UserModel> getUsers(RealmModel realm);
+    List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm);
+
+    List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults);
+    List<UserModel> searchForUser(String search, RealmModel realm, int firstResult, int maxResults);
+    List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults);
+
+
+
+
+
+    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/models/UserUpdateProvider.java b/server-spi/src/main/java/org/keycloak/models/UserUpdateProvider.java
new file mode 100644
index 0000000..a9efe97
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/UserUpdateProvider.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.models;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserUpdateProvider {
+    UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions);
+
+    UserModel addUser(RealmModel realm, String username);
+
+    boolean removeUser(RealmModel realm, UserModel user);
+
+    void grantToAllUsers(RealmModel realm, RoleModel role);
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java b/server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java
index 074949f..c6b3762 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java
@@ -48,7 +48,7 @@ public class CredentialValidation {
 
     }
 
-    /**
+   /**
      * Will update password if hash iteration policy has changed
      *
      * @param realm
@@ -195,7 +195,7 @@ public class CredentialValidation {
         return true;
     }
 
-    private static boolean validCredential(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel credential) {
+    public static boolean validCredential(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel credential) {
         if (credential.getType().equals(UserCredentialModel.PASSWORD)) {
             if (!validPassword(session, realm, user, credential.getValue())) {
                 return false;
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/FederatedCredentials.java b/server-spi/src/main/java/org/keycloak/models/utils/FederatedCredentials.java
new file mode 100644
index 0000000..69a823c
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/utils/FederatedCredentials.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.models.utils;
+
+import org.keycloak.common.util.Time;
+import org.keycloak.hash.PasswordHashManager;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.OTPPolicy;
+import org.keycloak.models.PasswordPolicy;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.storage.federated.UserFederatedStorageProvider;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class FederatedCredentials {
+    public static void updateCredential(KeycloakSession session, UserFederatedStorageProvider provider, RealmModel realm, UserModel user, UserCredentialModel cred) {
+        if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
+            updatePasswordCredential(session, provider,realm, user, cred);
+        } else if (UserCredentialModel.isOtp(cred.getType())) {
+            updateOtpCredential(session, provider, realm, user, cred);
+        } else {
+            UserCredentialValueModel fedCred = getCredentialByType(provider, realm, user, cred.getType());
+            if (fedCred == null) {
+                fedCred.setCreatedDate(Time.toMillis(Time.currentTime()));
+                fedCred.setType(cred.getType());
+                fedCred.setDevice(cred.getDevice());
+                fedCred.setValue(cred.getValue());
+
+            } else {
+                fedCred.setValue(cred.getValue());
+            }
+            provider.updateCredential(realm, user, fedCred);
+        }
+    }
+
+    public static UserCredentialValueModel getCredentialByType(UserFederatedStorageProvider provider, RealmModel realm, UserModel user, String type) {
+        List<UserCredentialValueModel> creds = provider.getCredentials(realm, user);
+        for (UserCredentialValueModel cred : creds) {
+            if (cred.getType().equals(type)) return cred;
+        }
+        return null;
+    }
+
+    public static LinkedList<UserCredentialValueModel> getCredentialsByType(UserFederatedStorageProvider provider, RealmModel realm, UserModel user, String type) {
+        List<UserCredentialValueModel> creds = provider.getCredentials(realm, user);
+        LinkedList<UserCredentialValueModel> newCreds = new LinkedList<>();
+        for (UserCredentialValueModel cred : creds) {
+            if (cred.getType().equals(type)) newCreds.add(cred);
+        }
+        return newCreds;
+    }
+
+    public static void updatePasswordCredential(KeycloakSession session, UserFederatedStorageProvider provider, RealmModel realm, UserModel user, UserCredentialModel cred) {
+        UserCredentialValueModel fedCred = getCredentialByType(provider, realm, user, cred.getType());
+        if (fedCred == null) {
+            UserCredentialValueModel newCred = PasswordHashManager.encode(session, realm, cred.getValue());
+            newCred.setCreatedDate(Time.toMillis(Time.currentTime()));
+            newCred.setType(cred.getType());
+            newCred.setDevice(cred.getDevice());
+            provider.updateCredential(realm, user, newCred);
+        } else {
+            int expiredPasswordsPolicyValue = -1;
+            PasswordPolicy policy = realm.getPasswordPolicy();
+            if(policy != null) {
+                expiredPasswordsPolicyValue = policy.getExpiredPasswords();
+            }
+
+            if (expiredPasswordsPolicyValue != -1) {
+                fedCred.setType(UserCredentialModel.PASSWORD_HISTORY);
+
+                LinkedList<UserCredentialValueModel> credentialEntities = getCredentialsByType(provider, realm, user, UserCredentialModel.PASSWORD_HISTORY);
+                if (credentialEntities.size() > expiredPasswordsPolicyValue - 1) {
+                    Collections.sort(credentialEntities, new Comparator<UserCredentialValueModel>() {
+                        @Override
+                        public int compare(UserCredentialValueModel o1, UserCredentialValueModel o2) {
+                            if (o1.getCreatedDate().equals(o2.getCreatedDate())) return 0;
+                            return o1.getCreatedDate() < o2.getCreatedDate() ? -1 : 1;
+                        }
+                    });
+                    while (credentialEntities.size() > expiredPasswordsPolicyValue - 1) {
+                        UserCredentialValueModel model = credentialEntities.removeFirst();
+                        provider.removeCredential(realm, user, model);
+                    }
+
+                }
+                provider.updateCredential(realm, user, fedCred);
+                fedCred = PasswordHashManager.encode(session, realm, cred.getValue());
+                fedCred.setCreatedDate(Time.toMillis(Time.currentTime()));
+                fedCred.setType(cred.getType());
+                fedCred.setDevice(cred.getDevice());
+                provider.updateCredential(realm, user, fedCred);
+            } else {
+                // clear password history as it is not required anymore
+                for (UserCredentialValueModel model : getCredentialsByType(provider, realm, user, UserCredentialModel.PASSWORD_HISTORY)) {
+                    provider.removeCredential(realm, user, model);
+                }
+                UserCredentialValueModel newCred = PasswordHashManager.encode(session, realm, cred.getValue());
+                newCred.setCreatedDate(Time.toMillis(Time.currentTime()));
+                newCred.setType(cred.getType());
+                newCred.setDevice(cred.getDevice());
+                newCred.setId(fedCred.getId());
+                provider.updateCredential(realm, user, newCred);
+            }
+
+
+        }
+
+
+    }
+
+    public static  void updateOtpCredential(KeycloakSession session, UserFederatedStorageProvider provider, RealmModel realm, UserModel user, UserCredentialModel cred) {
+        LinkedList<UserCredentialValueModel> credentialEntities = getCredentialsByType(provider, realm, user, UserCredentialModel.PASSWORD_HISTORY);
+
+        if (credentialEntities.isEmpty()) {
+            UserCredentialValueModel fedCred = new UserCredentialValueModel();
+            fedCred.setCreatedDate(Time.toMillis(Time.currentTime()));
+            fedCred.setType(cred.getType());
+            fedCred.setDevice(cred.getDevice());
+            fedCred.setValue(cred.getValue());
+            OTPPolicy otpPolicy = realm.getOTPPolicy();
+            fedCred.setAlgorithm(otpPolicy.getAlgorithm());
+            fedCred.setDigits(otpPolicy.getDigits());
+            fedCred.setCounter(otpPolicy.getInitialCounter());
+            fedCred.setPeriod(otpPolicy.getPeriod());
+            provider.updateCredential(realm, user, fedCred);
+        } else {
+            OTPPolicy policy = realm.getOTPPolicy();
+            if (cred.getDevice() == null) {
+                for (UserCredentialValueModel model : credentialEntities) provider.removeCredential(realm, user, model);
+                UserCredentialValueModel fedCred = new UserCredentialValueModel();
+                fedCred.setCreatedDate(Time.toMillis(Time.currentTime()));
+                fedCred.setType(cred.getType());
+                fedCred.setDevice(cred.getDevice());
+                fedCred.setDigits(policy.getDigits());
+                fedCred.setCounter(policy.getInitialCounter());
+                fedCred.setAlgorithm(policy.getAlgorithm());
+                fedCred.setValue(cred.getValue());
+                fedCred.setPeriod(policy.getPeriod());
+                provider.updateCredential(realm, user, fedCred);
+            } else {
+                UserCredentialValueModel fedCred = new UserCredentialValueModel();
+                for (UserCredentialValueModel model : credentialEntities) {
+                    if (cred.getDevice().equals(model.getDevice())) {
+                        fedCred = model;
+                        break;
+                    }
+                }
+                fedCred.setCreatedDate(Time.toMillis(Time.currentTime()));
+                fedCred.setType(cred.getType());
+                fedCred.setDevice(cred.getDevice());
+                fedCred.setDigits(policy.getDigits());
+                fedCred.setCounter(policy.getInitialCounter());
+                fedCred.setAlgorithm(policy.getAlgorithm());
+                fedCred.setValue(cred.getValue());
+                fedCred.setPeriod(policy.getPeriod());
+                provider.updateCredential(realm, user, fedCred);
+            }
+        }
+    }
+
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/FederatedCredentialValidation.java b/server-spi/src/main/java/org/keycloak/models/utils/FederatedCredentialValidation.java
new file mode 100755
index 0000000..54df576
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/utils/FederatedCredentialValidation.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.utils;
+
+import org.keycloak.common.util.Time;
+import org.keycloak.hash.PasswordHashManager;
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.JWSInputException;
+import org.keycloak.jose.jws.crypto.RSAProvider;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.OTPPolicy;
+import org.keycloak.models.PasswordPolicy;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.representations.PasswordToken;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class FederatedCredentialValidation {
+
+    private static int hashIterations(RealmModel realm) {
+        PasswordPolicy policy = realm.getPasswordPolicy();
+        if (policy != null) {
+            return policy.getHashIterations();
+        }
+        return -1;
+
+    }
+
+   /**
+     * Will update password if hash iteration policy has changed
+     *
+     * @param realm
+     * @param user
+     * @param password
+     * @return
+     */
+    public static boolean validPassword(KeycloakSession session, RealmModel realm, UserModel user, String password, UserCredentialValueModel fedCred) {
+        return validateHashedCredential(session, realm, user, password, fedCred);
+
+    }
+
+
+    public static boolean validateHashedCredential(KeycloakSession session, RealmModel realm, UserModel user, String unhashedCredValue, UserCredentialValueModel credential) {
+        if (unhashedCredValue == null || unhashedCredValue.isEmpty()) {
+            return false;
+        }
+
+        boolean validated = PasswordHashManager.verify(session, realm, unhashedCredValue, credential);
+
+        if (validated) {
+            int iterations = hashIterations(realm);
+            if (iterations > -1 && iterations != credential.getHashIterations()) {
+
+                UserCredentialValueModel newCred = PasswordHashManager.encode(session, realm, unhashedCredValue);
+                session.userFederatedStorage().updateCredential(realm, user, newCred);
+            }
+
+        }
+        return validated;
+    }
+
+    public static boolean validPasswordToken(RealmModel realm, UserModel user, String encodedPasswordToken) {
+        try {
+            JWSInput jws = new JWSInput(encodedPasswordToken);
+            if (!RSAProvider.verify(jws, realm.getPublicKey())) {
+                return false;
+            }
+            PasswordToken passwordToken = jws.readJsonContent(PasswordToken.class);
+            if (!passwordToken.getRealm().equals(realm.getName())) {
+                return false;
+            }
+            if (!passwordToken.getUser().equals(user.getId())) {
+                return false;
+            }
+            if (Time.currentTime() - passwordToken.getTimestamp() > realm.getAccessCodeLifespanUserAction()) {
+                return false;
+            }
+            return true;
+        } catch (JWSInputException e) {
+            return false;
+        }
+    }
+
+    public static boolean validHOTP(KeycloakSession session, RealmModel realm, UserModel user, String otp, List<UserCredentialValueModel> fedCreds) {
+        UserCredentialValueModel passwordCred = null;
+        OTPPolicy policy = realm.getOTPPolicy();
+        HmacOTP validator = new HmacOTP(policy.getDigits(), policy.getAlgorithm(), policy.getLookAheadWindow());
+        for (UserCredentialValueModel cred : fedCreds) {
+            if (cred.getType().equals(UserCredentialModel.HOTP)) {
+                int counter = validator.validateHOTP(otp, cred.getValue(), cred.getCounter());
+                if (counter < 0) return false;
+                cred.setCounter(counter);
+                session.userFederatedStorage().updateCredential(realm, user, cred);
+                return true;
+            }
+        }
+        return false;
+
+    }
+
+    public static boolean validTOTP(RealmModel realm, UserModel user, String otp, List<UserCredentialValueModel> fedCreds) {
+        UserCredentialValueModel passwordCred = null;
+        OTPPolicy policy = realm.getOTPPolicy();
+        TimeBasedOTP validator = new TimeBasedOTP(policy.getAlgorithm(), policy.getDigits(), policy.getPeriod(), policy.getLookAheadWindow());
+        for (UserCredentialValueModel cred : fedCreds) {
+            if (validator.validateTOTP(otp, cred.getValue().getBytes())) {
+                return true;
+            }
+        }
+        return false;
+
+    }
+    public static boolean validSecret(RealmModel realm, UserModel user, String secret, UserCredentialValueModel cred) {
+        return cred.getValue().equals(secret);
+
+    }
+
+    public static boolean validCredential(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel credential, List<UserCredentialValueModel> fedCreds) {
+        if (credential.getType().equals(UserCredentialModel.PASSWORD)) {
+            if (!validPassword(session, realm, user, credential.getValue(), fedCreds.get(0))) {
+                return false;
+            }
+        } else if (credential.getType().equals(UserCredentialModel.PASSWORD_TOKEN)) {
+            if (!validPasswordToken(realm, user, credential.getValue())) {
+                return false;
+            }
+        } else if (credential.getType().equals(UserCredentialModel.TOTP)) {
+            if (!validTOTP(realm, user, credential.getValue(), fedCreds)) {
+                return false;
+            }
+        } else if (credential.getType().equals(UserCredentialModel.HOTP)) {
+            if (!validHOTP(session, realm, user, credential.getValue(), fedCreds)) {
+                return false;
+            }
+        } else if (credential.getType().equals(UserCredentialModel.SECRET)) {
+            if (!validSecret(realm, user, credential.getValue(), fedCreds.get(0))) {
+                return false;
+            }
+        } else {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
index 819c0db..55a7c02 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
@@ -44,6 +44,7 @@ import org.keycloak.models.UserModel;
 import org.keycloak.representations.idm.CertificateRepresentation;
 import org.keycloak.common.util.CertificateUtils;
 import org.keycloak.common.util.PemUtils;
+import org.keycloak.storage.StorageProviderModel;
 
 import javax.crypto.spec.SecretKeySpec;
 import java.io.IOException;
@@ -349,6 +350,43 @@ public final class KeycloakModelUtils {
      * @param federationProviders
      * @throws ModelDuplicateException if there is other provider with same displayName
      */
+    public static void ensureUniqueDisplayName(String displayName, StorageProviderModel myProvider, List<StorageProviderModel> federationProviders) throws ModelDuplicateException {
+        if (displayName != null) {
+
+            for (StorageProviderModel federationProvider : federationProviders) {
+                if (myProvider != null && (myProvider.equals(federationProvider) || (myProvider.getId() != null && myProvider.getId().equals(federationProvider.getId())))) {
+                    continue;
+                }
+
+                if (displayName.equals(federationProvider.getDisplayName())) {
+                    throw new ModelDuplicateException("There is already existing federation provider with display name: " + displayName);
+                }
+            }
+        }
+    }
+
+
+    public static StorageProviderModel findStorageProviderByDisplayName(String displayName, RealmModel realm) {
+        if (displayName == null) {
+            return null;
+        }
+
+        for (StorageProviderModel provider : realm.getStorageProviders()) {
+            if (displayName.equals(provider.getDisplayName())) {
+                return provider;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Ensure that displayName of myProvider (if not null) is unique and there is no other provider with same displayName in the list.
+     *
+     * @param displayName         to check for duplications
+     * @param myProvider          provider, which is excluded from the list (if present)
+     * @param federationProviders
+     * @throws ModelDuplicateException if there is other provider with same displayName
+     */
     public static void ensureUniqueDisplayName(String displayName, UserFederationProviderModel myProvider, List<UserFederationProviderModel> federationProviders) throws ModelDuplicateException {
         if (displayName != null) {
 
@@ -378,7 +416,6 @@ public final class KeycloakModelUtils {
         return null;
     }
 
-
     public static UserFederationProviderModel findUserFederationProviderById(String fedProviderId, RealmModel realm) {
         for (UserFederationProviderModel fedProvider : realm.getUserFederationProviders()) {
             if (fedProviderId.equals(fedProvider.getId())) {
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index 2516105..b36b96c 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -1299,7 +1299,7 @@ public class RepresentationToModel {
         if (userRep.getClientConsents() != null) {
             for (UserConsentRepresentation consentRep : userRep.getClientConsents()) {
                 UserConsentModel consentModel = toModel(newRealm, consentRep);
-                user.addConsent(consentModel);
+                session.userStorage().addConsent(newRealm, user, consentModel);
             }
         }
         if (userRep.getServiceAccountClientId() != null) {
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/UserModelDelegate.java b/server-spi/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
index f2d66e2..9a8e4ab 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
@@ -235,31 +235,6 @@ public class UserModelDelegate implements UserModel {
         delegate.setServiceAccountClientLink(clientInternalId);
     }
 
-    @Override
-    public void addConsent(UserConsentModel consent) {
-        delegate.addConsent(consent);
-    }
-
-    @Override
-    public UserConsentModel getConsentByClient(String clientId) {
-        return delegate.getConsentByClient(clientId);
-    }
-
-    @Override
-    public List<UserConsentModel> getConsents() {
-        return delegate.getConsents();
-    }
-
-    @Override
-    public void updateConsent(UserConsentModel consent) {
-        delegate.updateConsent(consent);
-    }
-
-    @Override
-    public boolean revokeConsentForClient(String clientId) {
-        return delegate.revokeConsentForClient(clientId);
-    }
-
     public UserModel getDelegate() {
         return delegate;
     }
diff --git a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java
new file mode 100644
index 0000000..114b0c3
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.adapter;
+
+import org.keycloak.models.ClientModel;
+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.UserCredentialValueModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.storage.federated.UserFederatedStorageProvider;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public abstract class AbstractUserAdapter implements UserModel {
+    protected KeycloakSession session;
+    protected RealmModel realm;
+
+    public UserFederatedStorageProvider getFederatedStorage() {
+        return null;
+    }
+
+    @Override
+    public Set<String> getRequiredActions() {
+        return getFederatedStorage().getRequiredActions(realm, this);
+    }
+
+    @Override
+    public void addRequiredAction(String action) {
+        getFederatedStorage().addRequiredAction(realm, this, action);
+
+    }
+
+    @Override
+    public void removeRequiredAction(String action) {
+        getFederatedStorage().removeRequiredAction(realm, this, action);
+
+    }
+
+    @Override
+    public void addRequiredAction(RequiredAction action) {
+        getFederatedStorage().addRequiredAction(realm, this, action.name());
+
+    }
+
+    @Override
+    public void removeRequiredAction(RequiredAction action) {
+        getFederatedStorage().removeRequiredAction(realm, this, action.name());
+    }
+
+    @Override
+    public Set<GroupModel> getGroups() {
+        return null;
+    }
+
+    @Override
+    public void joinGroup(GroupModel group) {
+
+    }
+
+    @Override
+    public void leaveGroup(GroupModel group) {
+
+    }
+
+    @Override
+    public boolean isMemberOf(GroupModel group) {
+        return false;
+    }
+
+    @Override
+    public String getFederationLink() {
+        return null;
+    }
+
+    @Override
+    public void setFederationLink(String link) {
+
+    }
+
+    @Override
+    public String getServiceAccountClientLink() {
+        return null;
+    }
+
+    @Override
+    public void setServiceAccountClientLink(String clientInternalId) {
+
+    }
+
+    @Override
+    public Set<RoleModel> getRealmRoleMappings() {
+        return null;
+    }
+
+    @Override
+    public Set<RoleModel> getClientRoleMappings(ClientModel app) {
+        return null;
+    }
+
+    @Override
+    public boolean hasRole(RoleModel role) {
+        return false;
+    }
+
+    @Override
+    public void grantRole(RoleModel role) {
+
+    }
+
+    @Override
+    public Set<RoleModel> getRoleMappings() {
+        return null;
+    }
+
+    @Override
+    public void deleteRoleMapping(RoleModel role) {
+
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return false;
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+
+    }
+
+    @Override
+    public boolean isOtpEnabled() {
+        return false;
+    }
+
+    @Override
+    public void setOtpEnabled(boolean totp) {
+
+    }
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/changeset/UserData.java b/server-spi/src/main/java/org/keycloak/storage/changeset/UserData.java
new file mode 100755
index 0000000..4b2813a
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/changeset/UserData.java
@@ -0,0 +1,387 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.storage.changeset;
+
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.models.entities.AbstractIdentifiableEntity;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UserData {
+
+    private String id;
+    private boolean idChanged;
+    private String username;
+    private boolean usernameChanged;
+    private Long createdTimestamp;
+    private boolean createdTimestampChanged;
+    private String firstName;
+    private boolean firstNameChanged;
+    private String lastName;
+    private boolean lastNameChanged;
+    private String email;
+    private boolean emailChanged;
+    private boolean emailVerified;
+    private boolean emailVerifiedChanged;
+    private boolean totp;
+    private boolean totpChanged;
+    private boolean enabled;
+    private boolean enabledChanged;
+
+    private Set<String> roleIds = new HashSet<>();
+    private boolean rolesChanged;
+    private Set<String> groupIds = new HashSet<>();
+    private boolean groupsChanged;
+
+    private MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
+    private boolean attributesChanged;
+    private Set<String> requiredActions = new HashSet<>();
+    private boolean requiredActionsChanged;
+    private List<UserCredentialValueModel> credentials = new LinkedList<>();
+    private boolean credentialsChanged;
+
+    public void rememberState() {
+        original = new UserData();
+        original.id = id;
+        original.username = username;
+        original.createdTimestamp = createdTimestamp;
+        original.firstName = firstName;
+        original.lastName = lastName;
+        original.email = email;
+        original.emailVerified = emailVerified;
+        original.totp = totp;
+        original.enabled = enabled;
+        original.attributes.putAll(attributes);
+        original.requiredActions.addAll(requiredActions);
+        original.credentials.addAll(credentials);
+    }
+
+    private UserData original = null;
+
+    public void clearChangeFlags() {
+        original = null;
+        idChanged = false;
+        usernameChanged = false;
+        createdTimestampChanged = false;
+        firstNameChanged = false;
+        lastNameChanged = false;
+        emailChanged = false;
+        emailVerifiedChanged = false;
+        totpChanged = false;
+        enabledChanged = false;
+        rolesChanged = false;
+        groupsChanged = false;
+        attributesChanged = false;
+        requiredActionsChanged = false;
+        credentialsChanged = false;
+    }
+
+    public boolean isChanged() {
+        return !idChanged
+        && !usernameChanged
+        && !createdTimestampChanged
+        && !firstNameChanged
+        && !lastNameChanged
+        && !emailChanged
+        && !emailVerifiedChanged
+        && !totpChanged
+        && !enabledChanged
+        && !rolesChanged
+        && !groupsChanged
+        && !attributesChanged
+        && !requiredActionsChanged
+        && !credentialsChanged;
+    }
+
+    public boolean isIdChanged() {
+        return idChanged;
+    }
+
+    public boolean isUsernameChanged() {
+        return usernameChanged;
+    }
+
+    public boolean isCreatedTimestampChanged() {
+        return createdTimestampChanged;
+    }
+
+    public boolean isFirstNameChanged() {
+        return firstNameChanged;
+    }
+
+    public boolean isLastNameChanged() {
+        return lastNameChanged;
+    }
+
+    public boolean isEmailChanged() {
+        return emailChanged;
+    }
+
+    public boolean isEmailVerifiedChanged() {
+        return emailVerifiedChanged;
+    }
+
+    public boolean isTotpChanged() {
+        return totpChanged;
+    }
+
+    public boolean isEnabledChanged() {
+        return enabledChanged;
+    }
+
+    public boolean isRolesChanged() {
+        return rolesChanged;
+    }
+
+    public boolean isGroupsChanged() {
+        return groupsChanged;
+    }
+
+    public boolean isAttributesChanged() {
+        return attributesChanged;
+    }
+
+    public boolean isRequiredActionsChanged() {
+        return requiredActionsChanged;
+    }
+
+    public boolean isCredentialsChanged() {
+        return credentialsChanged;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+        idChanged = true;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+        usernameChanged = true;
+    }
+    
+    public Long getCreatedTimestamp() {
+        return createdTimestamp;
+    }
+
+    public void setCreatedTimestamp(Long timestamp) {
+        this.createdTimestamp = timestamp;
+        createdTimestampChanged = true;
+    }
+
+
+    public String getFirstName() {
+        return firstName;
+    }
+
+    public void setFirstName(String firstName) {
+        this.firstName = firstName;
+        firstNameChanged = true;
+    }
+
+    public String getLastName() {
+        return lastName;
+    }
+
+    public void setLastName(String lastName) {
+        this.lastName = lastName;
+        lastNameChanged = true;
+    }
+
+    public String getEmail() {
+        return email;
+    }
+
+    public void setEmail(String email) {
+        this.email = email;
+        emailChanged = true;
+    }
+
+    public boolean isEmailVerified() {
+        return emailVerified;
+    }
+
+    public void setEmailVerified(boolean emailVerified) {
+        this.emailVerified = emailVerified;
+        emailVerifiedChanged = true;
+    }
+
+    public boolean isTotp() {
+        return totp;
+    }
+
+    public void setTotp(boolean totp) {
+        this.totp = totp;
+        totpChanged = true;
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+        enabledChanged = true;
+    }
+
+    public Set<String> getRoleMappings() {
+        return Collections.unmodifiableSet(roleIds);
+    }
+
+    public void grantRole(String roleId) {
+        if (roleIds.contains(roleId)) return;
+        roleIds.add(roleId);
+        rolesChanged = true;
+    }
+
+    public void deleteRoleMapping(String roleId) {
+        if (!roleIds.contains(roleId)) return;
+        roleIds.remove(roleId);
+        rolesChanged = true;
+    }
+
+    public MultivaluedHashMap<String, String> getAttributes() {
+        return attributes;
+    }
+
+    public void setSingleAttribute(String name, String value) {
+        attributes.putSingle(name, value);
+        attributesChanged = true;
+
+    }
+    public void setAttribute(String name, List<String> values) {
+        attributes.put(name, values);
+        attributesChanged = true;
+    }
+    public void removeAttribute(String name) {
+        attributes.remove(name);
+        attributesChanged = true;
+    }
+
+
+
+    public Set<String> getRequiredActions() {
+        return Collections.unmodifiableSet(requiredActions);
+    }
+    public void addRequiredAction(String action) {
+        if (requiredActions.contains(action)) return;
+        requiredActions.add(action);
+        requiredActionsChanged = true;
+    }
+    public void removeRequiredAction(String action) {
+        if (!requiredActions.contains(action)) return;
+        requiredActions.remove(action);
+        requiredActionsChanged = true;
+    }
+
+    public List<UserCredentialValueModel> getCredentials() {
+        return Collections.unmodifiableList(credentials);
+    }
+
+    public void removeCredentialType(String type) {
+        Iterator<UserCredentialValueModel> it = credentials.iterator();
+        while (it.hasNext()) {
+            if (it.next().getType().equals(type)) {
+                it.remove();
+                credentialsChanged = true;
+            }
+        }
+
+    }
+
+    public void removeCredentialDevice(String type, String device) {
+        Iterator<UserCredentialValueModel> it = credentials.iterator();
+        while (it.hasNext()) {
+            UserCredentialValueModel next = it.next();
+            if (next.getType().equals(type) && next.getDevice().equals(device)) {
+                it.remove();
+                credentialsChanged = true;
+            }
+        }
+
+    }
+
+    public void setCredential(UserCredentialValueModel cred) {
+        removeCredentialType(cred.getType());
+        addCredential(cred);
+    }
+    public void addCredential(UserCredentialValueModel cred) {
+        credentials.add(cred);
+        credentialsChanged = true;
+    }
+
+    public Set<String> getGroupIds() {
+        return Collections.unmodifiableSet(groupIds);
+    }
+
+    public void joinGroup(String groupId) {
+        if (groupIds.contains(groupId)) return;
+        groupIds.add(groupId);
+        groupsChanged = true;
+    }
+
+    public void leaveGroup(String groupId) {
+        if (!groupIds.contains(groupId)) return;
+        groupIds.remove(groupId);
+        groupsChanged = true;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) return true;
+
+        if (this.id == null) return false;
+
+        if (o == null || getClass() != o.getClass()) return false;
+
+        AbstractIdentifiableEntity that = (AbstractIdentifiableEntity) o;
+
+        if (!getId().equals(that.getId())) return false;
+
+        return true;
+
+    }
+
+    @Override
+    public int hashCode() {
+        return id!=null ? id.hashCode() : super.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%s [ id=%s ]", getClass().getSimpleName(), getId());
+    }
+
+}
+
diff --git a/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataAdapter.java b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataAdapter.java
new file mode 100644
index 0000000..6ec78f2
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataAdapter.java
@@ -0,0 +1,340 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.changeset;
+
+import org.keycloak.common.util.Time;
+import org.keycloak.hash.PasswordHashManager;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.OTPPolicy;
+import org.keycloak.models.PasswordPolicy;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleContainerModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ *
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UserDataAdapter implements UserModel {
+    protected UserData userData;
+    protected RealmModel realm;
+    protected KeycloakSession session;
+    protected Set<String> managedCredentialTypes;
+    protected List<UserCredentialModel> updatedManagedCredentials = new LinkedList<>();
+
+    public UserDataAdapter(KeycloakSession session, RealmModel realm, UserData userData) {
+        this.session = session;
+        this.realm = realm;
+        this.userData = userData;
+        this.userData.rememberState();
+    }
+
+    @Override
+    public String getId() {
+        return userData.getId();
+    }
+
+    @Override
+    public String getUsername() {
+        return userData.getUsername();
+    }
+
+    @Override
+    public void setUsername(String username) {
+        userData.setUsername(username);
+
+    }
+
+    @Override
+    public Long getCreatedTimestamp() {
+        return userData.getCreatedTimestamp();
+    }
+
+    @Override
+    public void setCreatedTimestamp(Long timestamp) {
+        userData.setCreatedTimestamp(timestamp);
+
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return userData.isEnabled();
+    }
+
+    @Override
+    public boolean isOtpEnabled() {
+        return userData.isTotp();
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        userData.setEnabled(enabled);
+
+    }
+
+    @Override
+    public void setSingleAttribute(String name, String value) {
+        userData.setSingleAttribute(name, value);
+
+    }
+
+    @Override
+    public void setAttribute(String name, List<String> values) {
+        userData.setAttribute(name, values);
+
+    }
+
+    @Override
+    public void removeAttribute(String name) {
+        userData.removeAttribute(name);
+
+    }
+
+    @Override
+    public String getFirstAttribute(String name) {
+        return userData.getAttributes().getFirst(name);
+    }
+
+    @Override
+    public List<String> getAttribute(String name) {
+        return userData.getAttributes().get(name);
+    }
+
+    @Override
+    public Map<String, List<String>> getAttributes() {
+        return userData.getAttributes();
+    }
+
+    @Override
+    public Set<String> getRequiredActions() {
+        return userData.getRequiredActions();
+    }
+
+    @Override
+    public void addRequiredAction(String action) {
+        userData.addRequiredAction(action);
+
+    }
+
+    @Override
+    public void removeRequiredAction(String action) {
+        userData.removeRequiredAction(action);
+
+    }
+
+    @Override
+    public void addRequiredAction(RequiredAction action) {
+        userData.addRequiredAction(action.name());
+
+    }
+
+    @Override
+    public void removeRequiredAction(RequiredAction action) {
+        userData.removeRequiredAction(action.name());
+
+    }
+
+    @Override
+    public String getFirstName() {
+        return userData.getFirstName();
+    }
+
+    @Override
+    public void setFirstName(String firstName) {
+        userData.setFirstName(firstName);
+
+    }
+
+    @Override
+    public String getLastName() {
+        return userData.getLastName();
+    }
+
+    @Override
+    public void setLastName(String lastName) {
+        userData.setLastName(lastName);
+
+    }
+
+    @Override
+    public String getEmail() {
+        return userData.getEmail();
+    }
+
+    @Override
+    public void setEmail(String email) {
+        userData.setEmail(email);
+
+    }
+
+    @Override
+    public boolean isEmailVerified() {
+        return userData.isEmailVerified();
+    }
+
+    @Override
+    public void setEmailVerified(boolean verified) {
+        userData.setEmailVerified(verified);
+
+    }
+
+    @Override
+    public void setOtpEnabled(boolean totp) {
+        userData.setTotp(totp);
+
+    }
+
+    @Override
+    public void updateCredential(UserCredentialModel cred) {
+
+    }
+
+    @Override
+    public List<UserCredentialValueModel> getCredentialsDirectly() {
+        return null;
+    }
+
+    @Override
+    public void updateCredentialDirectly(UserCredentialValueModel cred) {
+
+    }
+
+    @Override
+    public Set<GroupModel> getGroups() {
+        Set<String> groups = userData.getGroupIds();
+        Set<GroupModel> set = new HashSet<>();
+        for (String id : groups) {
+            GroupModel group = realm.getGroupById(id);
+            if (group != null) set.add(group);
+        }
+        return set;
+    }
+
+    @Override
+    public void joinGroup(GroupModel group) {
+        userData.joinGroup(group.getId());
+
+    }
+
+    @Override
+    public void leaveGroup(GroupModel group) {
+        userData.leaveGroup(group.getId());
+
+    }
+
+    @Override
+    public boolean isMemberOf(GroupModel group) {
+        Set<GroupModel> roles = getGroups();
+        return KeycloakModelUtils.isMember(roles, group);
+    }
+
+    @Override
+    public String getFederationLink() {
+        return null;
+    }
+
+    @Override
+    public void setFederationLink(String link) {
+
+    }
+
+    @Override
+    public String getServiceAccountClientLink() {
+        return null;
+    }
+
+    @Override
+    public void setServiceAccountClientLink(String clientInternalId) {
+
+    }
+
+    @Override
+    public Set<RoleModel> getRealmRoleMappings() {
+        Set<RoleModel> roleMappings = getRoleMappings();
+
+        Set<RoleModel> realmRoles = new HashSet<RoleModel>();
+        for (RoleModel role : roleMappings) {
+            RoleContainerModel container = role.getContainer();
+            if (container instanceof RealmModel) {
+                realmRoles.add(role);
+            }
+        }
+        return realmRoles;
+    }
+
+    @Override
+    public Set<RoleModel> getClientRoleMappings(ClientModel app) {
+        Set<RoleModel> roleMappings = getRoleMappings();
+
+        Set<RoleModel> roles = new HashSet<RoleModel>();
+        for (RoleModel role : roleMappings) {
+            RoleContainerModel container = role.getContainer();
+            if (container instanceof ClientModel) {
+                ClientModel appModel = (ClientModel)container;
+                if (appModel.getId().equals(app.getId())) {
+                    roles.add(role);
+                }
+            }
+        }
+        return roles;
+    }
+
+    @Override
+    public boolean hasRole(RoleModel role) {
+        Set<RoleModel> roles = getRoleMappings();
+        return KeycloakModelUtils.hasRole(roles, role);
+    }
+
+    @Override
+    public void grantRole(RoleModel role) {
+        userData.grantRole(role.getId());
+
+    }
+
+    @Override
+    public Set<RoleModel> getRoleMappings() {
+        Set<String> roles = userData.getRoleMappings();
+        Set<RoleModel> set = new HashSet<>();
+        for (String id : roles) {
+            RoleModel role = realm.getRoleById(id);
+            if (role != null) set.add(role);
+        }
+        return set;
+    }
+
+    @Override
+    public void deleteRoleMapping(RoleModel role) {
+        userData.deleteRoleMapping(role.getId());
+    }
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataCredentialValidator.java b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataCredentialValidator.java
new file mode 100644
index 0000000..741f3b7
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataCredentialValidator.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.changeset;
+
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserDataCredentialValidator {
+    boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List<UserCredentialModel> input);
+    boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel... input);
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataLookup.java b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataLookup.java
new file mode 100644
index 0000000..5e80d5d
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataLookup.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.changeset;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.entities.UserEntity;
+import org.keycloak.storage.StorageId;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserDataLookup {
+    UserData getUserById(RealmModel realm, StorageId id);
+    UserData getUserByUsername(RealmModel realm, String username);
+    UserData getUserByEmail(RealmModel realm, String email);
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataQuery.java b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataQuery.java
new file mode 100644
index 0000000..e0ef387
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataQuery.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.changeset;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.entities.UserEntity;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserDataQuery {
+
+    // Service account is included for counts
+    int getUsersCount(RealmModel realm);
+
+    List<UserData> getUsers(RealmModel realm);
+    List<UserData> searchForUser(String search, RealmModel realm);
+    List<UserData> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm);
+
+    List<UserData> getUsers(RealmModel realm, int firstResult, int maxResults);
+    List<UserData> searchForUser(String search, RealmModel realm, int firstResult, int maxResults);
+    List<UserData> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults);
+
+
+
+    // Searching by UserModel.attribute (not property)
+    List<UserData> searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm);
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataStore.java b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataStore.java
new file mode 100644
index 0000000..e896b00
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataStore.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.changeset;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.entities.UserEntity;
+import org.keycloak.storage.StorageId;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserDataStore {
+    void updateUser(RealmModel realm, UserData user);
+    void addUser(RealmModel realm, UserData user);
+    boolean removeUser(RealmModel realm, StorageId store);
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/CredentialModel.java b/server-spi/src/main/java/org/keycloak/storage/federated/CredentialModel.java
new file mode 100644
index 0000000..7c4b735
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/federated/CredentialModel.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.federated;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class CredentialModel {
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserAttributeFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserAttributeFederatedStorage.java
new file mode 100644
index 0000000..12bcabf
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserAttributeFederatedStorage.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.federated;
+
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserAttributeFederatedStorage {
+    void setSingleAttribute(RealmModel realm, UserModel user, String name, String value);
+    void setAttribute(RealmModel realm, UserModel user, String name, List<String> values);
+    void removeAttribute(RealmModel realm, UserModel user, String name);
+    MultivaluedHashMap<String, String> getAttributes(RealmModel realm, UserModel user);
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserBrokerLinkFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserBrokerLinkFederatedStorage.java
new file mode 100644
index 0000000..6fcbabf
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserBrokerLinkFederatedStorage.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.federated;
+
+import org.keycloak.models.FederatedIdentityModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserBrokerLinkFederatedStorage {
+    String getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm);
+    public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel socialLink);
+    public boolean removeFederatedIdentity(RealmModel realm, UserModel user, String socialProvider);
+    void updateFederatedIdentity(RealmModel realm, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel);
+    Set<FederatedIdentityModel> getFederatedIdentities(UserModel user, RealmModel realm);
+    FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm);
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserConsentFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserConsentFederatedStorage.java
new file mode 100644
index 0000000..7a14b14
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserConsentFederatedStorage.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.federated;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserConsentModel;
+import org.keycloak.models.UserModel;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserConsentFederatedStorage {
+    void addConsent(RealmModel realm, UserModel user, UserConsentModel consent);
+    UserConsentModel getConsentByClient(RealmModel realm, UserModel user, String clientInternalId);
+    List<UserConsentModel> getConsents(RealmModel realm, UserModel user);
+    void updateConsent(RealmModel realm, UserModel user, UserConsentModel consent);
+    boolean revokeConsentForClient(RealmModel realm, UserModel user, String clientInternalId);
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserCredentialsFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserCredentialsFederatedStorage.java
new file mode 100644
index 0000000..7239182
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserCredentialsFederatedStorage.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.federated;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.models.UserModel;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserCredentialsFederatedStorage {
+    void updateCredential(RealmModel realm, UserModel user, UserCredentialModel cred);
+    void updateCredential(RealmModel realm, UserModel user, UserCredentialValueModel cred);
+    void removeCredential(RealmModel realm, UserModel user, UserCredentialValueModel cred);
+    List<UserCredentialValueModel> getCredentials(RealmModel realm, UserModel user);
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProvider.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProvider.java
new file mode 100755
index 0000000..46ae9db
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProvider.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.storage.federated;
+
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.provider.Provider;
+import org.keycloak.storage.StorageProviderModel;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserFederatedStorageProvider extends Provider,
+        UserAttributeFederatedStorage,
+        UserBrokerLinkFederatedStorage,
+        UserConsentFederatedStorage,
+        UserCredentialsFederatedStorage,
+        UserGroupMembershipFederatedStorage,
+        UserRequiredActionsFederatedStorage,
+        UserRoleMappingsFederatedStorage {
+
+    void preRemove(RealmModel realm);
+
+    void preRemove(RealmModel realm, UserFederationProviderModel link);
+
+    public void preRemove(RealmModel realm, GroupModel group);
+
+    void preRemove(RealmModel realm, RoleModel role);
+
+    void preRemove(RealmModel realm, ClientModel client);
+
+    void preRemove(ProtocolMapperModel protocolMapper);
+
+    void preRemove(RealmModel realm, UserModel user);
+
+    void preRemove(RealmModel realm, StorageProviderModel model);
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderFactory.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderFactory.java
new file mode 100644
index 0000000..667542c
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderFactory.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.federated;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserFederatedStorageProviderFactory extends ProviderFactory<UserFederatedStorageProvider> {
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderSpi.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderSpi.java
new file mode 100755
index 0000000..5475422
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderSpi.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.storage.federated;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+import org.keycloak.storage.StorageProvider;
+import org.keycloak.storage.StorageProviderFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class UserFederatedStorageProviderSpi implements Spi {
+
+    @Override
+    public boolean isInternal() {
+        return true;
+    }
+
+    @Override
+    public String getName() {
+        return "userFederatedStorage";
+    }
+
+    @Override
+    public Class<? extends Provider> getProviderClass() {
+        return UserFederatedStorageProvider.class;
+    }
+
+    @Override
+    public Class<? extends ProviderFactory> getProviderFactoryClass() {
+        return UserFederatedStorageProviderFactory.class;
+    }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserGroupMembershipFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserGroupMembershipFederatedStorage.java
new file mode 100644
index 0000000..fef3cee
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserGroupMembershipFederatedStorage.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.federated;
+
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserGroupMembershipFederatedStorage {
+    Set<GroupModel> getGroups(RealmModel realm, UserModel user);
+
+    void joinGroup(RealmModel realm,UserModel user, GroupModel group);
+
+    void leaveGroup(RealmModel realm,UserModel user, GroupModel group);
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserRequiredActionsFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserRequiredActionsFederatedStorage.java
new file mode 100644
index 0000000..1e14f78
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserRequiredActionsFederatedStorage.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.federated;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserRequiredActionsFederatedStorage {
+    Set<String> getRequiredActions(RealmModel realm, UserModel user);
+    void addRequiredAction(RealmModel realm,UserModel user, String action);
+    void removeRequiredAction(RealmModel realm,UserModel user, String action);
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserRoleMappingsFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserRoleMappingsFederatedStorage.java
new file mode 100644
index 0000000..f63d9ec
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserRoleMappingsFederatedStorage.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage.federated;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserModel;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserRoleMappingsFederatedStorage {
+
+    void grantRole(RealmModel realm,UserModel user, RoleModel role);
+
+    Set<RoleModel> getRoleMappings(RealmModel realm,UserModel user);
+
+    void deleteRoleMapping(RealmModel realm, UserModel user, RoleModel role);
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/StorageId.java b/server-spi/src/main/java/org/keycloak/storage/StorageId.java
new file mode 100644
index 0000000..4ecac6e
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/StorageId.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage;
+
+import org.keycloak.models.UserModel;
+
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class StorageId implements Serializable {
+    private String id;
+    private String providerId;
+    private String storageId;
+
+
+    public StorageId(String id) {
+        this.id = id;
+        if (!id.startsWith("f:")) {
+            storageId = id;
+            return;
+        }
+        int providerIndex = id.indexOf(':', 2);
+        providerId = id.substring(2, providerIndex);
+        storageId = id.substring(providerIndex + 1);
+
+    }
+
+    public static String resolveProviderId(UserModel user) {
+        return new StorageId(user.getId()).getProviderId();
+    }
+    public static boolean isLocalStorage(UserModel user) {
+        return new StorageId(user.getId()).getProviderId() == null;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public String getProviderId() {
+        return providerId;
+    }
+
+    public String getStorageId() {
+        return storageId;
+    }
+
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/StorageProvider.java b/server-spi/src/main/java/org/keycloak/storage/StorageProvider.java
new file mode 100644
index 0000000..6746d5d
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/StorageProvider.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage;
+
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.provider.Provider;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface StorageProvider extends Provider {
+    StorageProviderModel getModel();
+
+    void preRemove(RealmModel realm);
+    void preRemove(RealmModel realm, GroupModel group);
+    void preRemove(RealmModel realm, RoleModel role);
+    void preRemove(RealmModel realm, StorageProviderModel model);
+
+}
+
diff --git a/server-spi/src/main/java/org/keycloak/storage/StorageProviderFactory.java b/server-spi/src/main/java/org/keycloak/storage/StorageProviderFactory.java
new file mode 100755
index 0000000..3369da2
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/StorageProviderFactory.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.storage;
+
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.provider.ProviderFactory;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface StorageProviderFactory extends ProviderFactory<StorageProvider> {
+    boolean supports(Class<?> type);
+    /**
+     * called per Keycloak transaction.
+     *
+     * @param session
+     * @param model
+     * @return
+     */
+    StorageProvider getInstance(KeycloakSession session, StorageProviderModel model);
+
+    /**
+     * Config options to display in generic admin console page for federated
+     *
+     * @return
+     */
+    Set<String> getConfigurationOptions();
+
+    /**
+     * This is the name of the provider and will be showed in the admin console as an option.
+     *
+     * @return
+     */
+    @Override
+    String getId();
+
+    /**
+     * This method is never called and is only an artifact of ProviderFactory.  Returning null with no implementation is recommended.
+     * @param session
+     * @return
+     */
+    @Override
+    StorageProvider create(KeycloakSession session);
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/StorageProviderModel.java b/server-spi/src/main/java/org/keycloak/storage/StorageProviderModel.java
new file mode 100755
index 0000000..910df81
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/StorageProviderModel.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.storage;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Stored configuration of a User Storage provider instance.
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ * @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
+ */
+public class StorageProviderModel implements Serializable {
+
+    private String id;
+    private String providerName;
+    private Map<String, String> config = new HashMap<String, String>();
+    private int priority;
+    private String displayName;
+
+    public StorageProviderModel() {}
+
+    public StorageProviderModel(String id, String providerName, Map<String, String> config, int priority, String displayName) {
+        this.id = id;
+        this.providerName = providerName;
+        if (config != null) {
+           this.config.putAll(config);
+        }
+        this.priority = priority;
+        this.displayName = displayName;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getProviderName() {
+        return providerName;
+    }
+
+    public void setProviderName(String providerName) {
+        this.providerName = providerName;
+    }
+
+    public Map<String, String> getConfig() {
+        return config;
+    }
+
+    public void setConfig(Map<String, String> config) {
+        this.config = config;
+    }
+
+    public int getPriority() {
+        return priority;
+    }
+
+    public void setPriority(int priority) {
+        this.priority = priority;
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public void setDisplayName(String displayName) {
+        this.displayName = displayName;
+    }
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/StorageProviderSpi.java b/server-spi/src/main/java/org/keycloak/storage/StorageProviderSpi.java
new file mode 100755
index 0000000..5d6d0da
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/StorageProviderSpi.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.storage;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class StorageProviderSpi implements Spi {
+
+    @Override
+    public boolean isInternal() {
+        return true;
+    }
+
+    @Override
+    public String getName() {
+        return "storage";
+    }
+
+    @Override
+    public Class<? extends Provider> getProviderClass() {
+        return StorageProvider.class;
+    }
+
+    @Override
+    public Class<? extends ProviderFactory> getProviderFactoryClass() {
+        return StorageProviderFactory.class;
+    }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java b/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java
new file mode 100755
index 0000000..f740f76
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java
@@ -0,0 +1,580 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.storage;
+
+import org.jboss.logging.Logger;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.CredentialValidationOutput;
+import org.keycloak.models.FederatedIdentityModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserConsentModel;
+import org.keycloak.models.UserCredentialAuthenticationProvider;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserCredentialValidatorProvider;
+import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserLookupProvider;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserProvider;
+import org.keycloak.models.UserQueryProvider;
+import org.keycloak.models.UserUpdateProvider;
+import org.keycloak.models.utils.CredentialValidation;
+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;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UserStorageManager implements UserProvider {
+
+    private static final Logger logger = Logger.getLogger(UserStorageManager.class);
+
+    protected KeycloakSession session;
+
+    // Set of already validated/proxied federation users during this session. Key is user ID
+    private Map<String, UserModel> managedUsers = new HashMap<>();
+    private UserProvider localStorage = null;
+
+    public UserStorageManager(KeycloakSession session) {
+        this.session = session;
+    }
+
+    protected UserProvider localStorage() {
+        return session.userLocalStorage();
+    }
+
+    protected List<StorageProviderModel> getStorageProviders(RealmModel realm) {
+        return realm.getStorageProviders();
+    }
+
+    protected <T> T getFirstStorageProvider(RealmModel realm, Class<T> type) {
+        for (StorageProviderModel model : getStorageProviders(realm)) {
+            StorageProviderFactory factory = (StorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(StorageProvider.class, model.getProviderName());
+            if (factory.supports(type)) {
+                return type.cast(factory.getInstance(session, model));
+            }
+        }
+        return null;
+    }
+
+    protected <T> List<T> getStorageProviders(RealmModel realm, Class<T> type) {
+        List<T> list = new LinkedList<>();
+        for (StorageProviderModel model : getStorageProviders(realm)) {
+            StorageProviderFactory factory = (StorageProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(StorageProvider.class, model.getProviderName());
+            if (factory.supports(type)) {
+                list.add(type.cast(factory.getInstance(session, model)));
+            }
+
+
+        }
+        return list;
+    }
+
+
+    @Override
+    public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) {
+        UserUpdateProvider registry = getFirstStorageProvider(realm, UserUpdateProvider.class);
+        if (registry != null) {
+            return registry.addUser(realm, id, username, addDefaultRoles, addDefaultRequiredActions);
+        }
+        return localStorage().addUser(realm, id, username.toLowerCase(), addDefaultRoles, addDefaultRequiredActions);
+    }
+
+    @Override
+    public UserModel addUser(RealmModel realm, String username) {
+        UserUpdateProvider registry = getFirstStorageProvider(realm, UserUpdateProvider.class);
+        if (registry != null) {
+            return registry.addUser(realm, username);
+        }
+        return localStorage().addUser(realm, username.toLowerCase());
+    }
+
+    public StorageProvider getStorageProvider(StorageProviderModel model) {
+        StorageProviderFactory factory = (StorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(StorageProvider.class, model.getProviderName());
+        return factory.getInstance(session, model);
+    }
+
+    public StorageProvider getStorageProvider(RealmModel realm, String providerId) {
+        StorageProviderModel model = realm.getStorageProvider(providerId);
+        if (model == null) return null;
+        StorageProviderFactory factory = (StorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(StorageProvider.class, model.getProviderName());
+        if (factory == null) {
+            throw new ModelException("Could not find StorageProviderFactory for: " + model.getProviderName());
+        }
+        return factory.getInstance(session, model);
+    }
+
+    @Override
+    public boolean removeUser(RealmModel realm, UserModel user) {
+        StorageId storageId = new StorageId(user.getId());
+        if (storageId.getProviderId() == null) {
+            return localStorage().removeUser(realm, user);
+        }
+        UserUpdateProvider registry = (UserUpdateProvider)getStorageProvider(realm, storageId.getProviderId());
+        if (registry == null) {
+            throw new ModelException("Could not resolve StorageProvider: " + storageId.getProviderId());
+        }
+        return registry.removeUser(realm, user);
+
+    }
+
+    public UserFederatedStorageProvider getFederatedStorage() {
+        return session.userFederatedStorage();
+    }
+
+    @Override
+    public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel 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) {
+        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) {
+        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) {
+        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) {
+        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) {
+        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) {
+        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) {
+        if (StorageId.isLocalStorage(user)) {
+            return localStorage().revokeConsentForClient(realm, user, clientInternalId);
+        } else {
+            return getFederatedStorage().revokeConsentForClient(realm, user, clientInternalId);
+        }
+    }
+
+    @Override
+    public UserModel getUserById(String id, RealmModel realm) {
+        StorageId storageId = new StorageId(id);
+        if (storageId.getProviderId() == null) {
+            return localStorage().getUserById(id, realm);
+        }
+        UserLookupProvider provider = (UserLookupProvider)getStorageProvider(realm, storageId.getProviderId());
+        return provider.getUserById(id, realm);
+    }
+
+    @Override
+    public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group) {
+        return getGroupMembers(realm, group, -1, -1);
+    }
+
+    @Override
+    public UserModel getUserByUsername(String username, RealmModel realm) {
+        UserModel user = localStorage().getUserByUsername(username, realm);
+        if (user != null) return user;
+        for (UserLookupProvider provider : getStorageProviders(realm, UserLookupProvider.class)) {
+            user = provider.getUserByUsername(username, realm);
+            if (user != null) return user;
+        }
+        return null;
+    }
+
+    @Override
+    public UserModel getUserByEmail(String email, RealmModel realm) {
+        UserModel user = localStorage().getUserByEmail(email, realm);
+        if (user != null) return user;
+        for (UserLookupProvider provider : getStorageProviders(realm, UserLookupProvider.class)) {
+            user = provider.getUserByEmail(email, realm);
+            if (user != null) return user;
+        }
+        return null;
+    }
+
+    @Override
+    public UserModel getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm) {
+        UserModel user = localStorage().getUserByFederatedIdentity(socialLink, realm);
+        if (user != null) {
+            return user;
+        }
+        String id = getFederatedStorage().getUserByFederatedIdentity(socialLink, realm);
+        if (id != null) return getUserById(id, realm);
+        return null;
+    }
+
+    @Override
+    public UserModel getServiceAccount(ClientModel client) {
+        return localStorage().getServiceAccount(client);
+    }
+
+    @Override
+    public List<UserModel> getUsers(RealmModel realm, boolean includeServiceAccounts) {
+        return getUsers(realm, 0, Integer.MAX_VALUE - 1, includeServiceAccounts);
+
+    }
+
+    @Override
+    public List<UserModel> getUsers(RealmModel realm) {
+        return getUsers(realm, false);
+    }
+
+    @Override
+    public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults) {
+        return getUsers(realm, firstResult, maxResults, false);
+    }
+
+    @Override
+    public int getUsersCount(RealmModel realm) {
+        int size = localStorage().getUsersCount(realm);
+        for (UserQueryProvider provider : getStorageProviders(realm, UserQueryProvider.class)) {
+            size += provider.getUsersCount(realm);
+        }
+        return size;
+    }
+
+    interface PaginatedQuery {
+        List<UserModel> query(UserQueryProvider provider, int first, int max);
+    }
+
+    protected List<UserModel> query(PaginatedQuery pagedQuery, RealmModel realm, int firstResult, int maxResults) {
+        List<UserModel> results = new LinkedList<UserModel>();
+        if (maxResults == 0) return results;
+
+
+        List<UserQueryProvider> storageProviders = getStorageProviders(realm, UserQueryProvider.class);
+        LinkedList<UserQueryProvider> providers = new LinkedList<>();
+        if (providers.isEmpty()) {
+            return pagedQuery.query(localStorage(), firstResult, maxResults);
+        }
+        providers.add(localStorage());
+        providers.addAll(storageProviders);
+
+        int leftToRead = maxResults;
+        int leftToFirstResult = firstResult;
+
+        Iterator<UserQueryProvider> it = providers.iterator();
+        while (it.hasNext() && leftToRead != 0) {
+            UserQueryProvider provider = it.next();
+            boolean exhausted = false;
+            int index = 0;
+            if (leftToFirstResult > 0) {
+                do {
+                    int toRead = Math.min(50, leftToFirstResult);
+                    List<UserModel> tmp = pagedQuery.query(provider, index, toRead);
+                    leftToFirstResult -= tmp.size();
+                    index += tmp.size();
+                    if (tmp.size() < toRead) {
+                        exhausted = true;
+                        break;
+                    }
+                } while (leftToFirstResult > 0);
+            }
+            if (exhausted) continue;
+            List<UserModel> tmp = pagedQuery.query(provider, index, leftToRead);
+            results.addAll(tmp);
+            if (leftToRead > 0) leftToRead -= tmp.size();
+        }
+        return results;
+    }
+
+    @Override
+    public List<UserModel> getUsers(final RealmModel realm, int firstResult, int maxResults, final boolean includeServiceAccounts) {
+        return query(new PaginatedQuery() {
+            @Override
+            public List<UserModel> query(UserQueryProvider provider, int first, int max) {
+                 if (provider instanceof UserProvider) { // it is local storage
+                     return ((UserProvider)provider).getUsers(realm, first, max, includeServiceAccounts);
+                 }
+                return provider.getUsers(realm, first, max);
+            }
+        }, realm, firstResult, maxResults);
+    }
+
+    @Override
+    public List<UserModel> searchForUser(String search, RealmModel realm) {
+        return searchForUser(search, realm, 0, Integer.MAX_VALUE - 1);
+    }
+
+    @Override
+    public List<UserModel> searchForUser(final String search, final RealmModel realm, int firstResult, int maxResults) {
+         return query(new PaginatedQuery() {
+            @Override
+            public List<UserModel> query(UserQueryProvider provider, int first, int max) {
+                return provider.searchForUser(search, realm, first, max);
+            }
+        }, realm, firstResult, maxResults);
+    }
+
+    @Override
+    public List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm) {
+        return searchForUserByAttributes(attributes, realm, 0, Integer.MAX_VALUE - 1);
+    }
+
+    @Override
+    public List<UserModel> searchForUserByAttributes(final Map<String, String> attributes, final RealmModel realm, int firstResult, int maxResults) {
+        return query(new PaginatedQuery() {
+            @Override
+            public List<UserModel> query(UserQueryProvider provider, int first, int max) {
+                return provider.searchForUserByAttributes(attributes, realm, first, max);
+            }
+        }, realm, firstResult, maxResults);
+    }
+
+    @Override
+    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)) {
+            set.addAll(localStorage().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)) {
+            FederatedIdentityModel model = localStorage().getFederatedIdentity(user, socialProvider, realm);
+            if (model != null) return model;
+        }
+        return getFederatedStorage().getFederatedIdentity(user, socialProvider, realm);
+    }
+
+    @Override
+    public void grantToAllUsers(RealmModel realm, RoleModel role) {
+        // not federation-aware for now
+        List<UserUpdateProvider> storageProviders = getStorageProviders(realm, UserUpdateProvider.class);
+        LinkedList<UserUpdateProvider> providers = new LinkedList<>();
+        providers.add(localStorage());
+        providers.addAll(storageProviders);
+        for (UserUpdateProvider provider : providers) {
+            provider.grantToAllUsers(realm, role);
+        }
+    }
+
+    @Override
+    public List<UserModel> getGroupMembers(final RealmModel realm, final GroupModel group, int firstResult, int maxResults) {
+        return query(new PaginatedQuery() {
+            @Override
+            public List<UserModel> query(UserQueryProvider provider, int first, int max) {
+                return provider.getGroupMembers(realm, group, first, max);
+            }
+        }, realm, firstResult, maxResults);
+    }
+
+
+    @Override
+    public void preRemove(RealmModel realm) {
+        localStorage().preRemove(realm);
+        getFederatedStorage().preRemove(realm);
+        for (StorageProvider provider : getStorageProviders(realm, StorageProvider.class)) {
+            provider.preRemove(realm);
+        }
+    }
+
+    @Override
+    public void preRemove(RealmModel realm, UserFederationProviderModel model) {
+        getFederatedStorage().preRemove(realm, model);
+        localStorage().preRemove(realm, model);
+    }
+
+    @Override
+    public void preRemove(RealmModel realm, GroupModel group) {
+        localStorage().preRemove(realm, group);
+        getFederatedStorage().preRemove(realm, group);
+        for (StorageProvider provider : getStorageProviders(realm, StorageProvider.class)) {
+            provider.preRemove(realm, group);
+        }
+    }
+
+    @Override
+    public void preRemove(RealmModel realm, RoleModel role) {
+        localStorage().preRemove(realm, role);
+        getFederatedStorage().preRemove(realm, role);
+        for (StorageProvider provider : getStorageProviders(realm, StorageProvider.class)) {
+            provider.preRemove(realm, role);
+        }
+    }
+
+    @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
+    public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List<UserCredentialModel> input) {
+        if (StorageId.isLocalStorage(user)) {
+            return localStorage().validCredentials(session, realm, user, input);
+        }
+        // make sure we hit the cache here!
+        List<UserCredentialValueModel> userCreds = user.getCredentialsDirectly();
+
+        LinkedList<UserCredentialModel> toValidate = new LinkedList<>();
+        toValidate.addAll(input);
+        Iterator<UserCredentialModel> it = toValidate.iterator();
+        boolean failedStoredCredential = false;
+        // we allow for multiple credentials of same type, i.e. multiple OTP devices
+        while (it.hasNext()) {
+            UserCredentialModel cred = it.next();
+            boolean credValidated = false;
+            for (UserCredentialValueModel userCred : userCreds) {
+                if (!userCred.getType().equals(cred.getType())) continue;
+                if (CredentialValidation.validCredential(session, realm, user, cred)) {
+                    credValidated = true;
+                    break;
+                } else {
+                    failedStoredCredential = true;
+                }
+            }
+            if (credValidated) {
+                it.remove();
+            } else if (failedStoredCredential) {
+                return false;
+            }
+        }
+
+        if (toValidate.isEmpty()) return true;
+
+        StorageProvider provider = getStorageProvider(realm, StorageId.resolveProviderId(user));
+        if (!(provider instanceof UserCredentialValidatorProvider)) {
+            return false;
+        }
+        return ((UserCredentialValidatorProvider)provider).validCredentials(session, realm, user, toValidate);
+    }
+
+    @Override
+    public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel... input) {
+        return validCredentials(session, realm, user, Arrays.asList(input));
+    }
+
+    @Override
+    public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) {
+        List<UserCredentialAuthenticationProvider> providers = getStorageProviders(realm, UserCredentialAuthenticationProvider.class);
+        if (providers.isEmpty()) return CredentialValidationOutput.failed();
+
+        CredentialValidationOutput result = null;
+        for (UserCredentialModel cred : input) {
+            UserCredentialAuthenticationProvider providerSupportingCreds = null;
+
+            // Find first provider, which supports required credential type
+            for (UserCredentialAuthenticationProvider provider : providers) {
+                if (provider.getSupportedCredentialAuthenticationTypes().contains(cred.getType())) {
+                    providerSupportingCreds = provider;
+                    break;
+                }
+            }
+
+            if (providerSupportingCreds == null) {
+                logger.warn("Don't have provider supporting credentials of type " + cred.getType());
+                return CredentialValidationOutput.failed();
+            }
+
+            logger.debug("Found provider [" + providerSupportingCreds + "] supporting credentials of type " + cred.getType());
+            CredentialValidationOutput currentResult = providerSupportingCreds.validCredential(session, realm, cred);
+            result = (result == null) ? currentResult : result.merge(currentResult);
+        }
+
+        // For now, validCredentials(realm, input) is not supported for local userProviders
+        return (result != null) ? result : CredentialValidationOutput.failed();
+    }
+
+    @Override
+    public void preRemove(RealmModel realm, StorageProviderModel link) {
+
+    }
+
+    @Override
+    public void close() {
+    }
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/UserStorageProvider.java b/server-spi/src/main/java/org/keycloak/storage/UserStorageProvider.java
new file mode 100644
index 0000000..58695c3
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/storage/UserStorageProvider.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.storage;
+
+import org.keycloak.models.RealmModel;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserStorageProvider {
+    void preRemove(RealmModel realm);
+
+}
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 9a84898..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,8 @@
 #
 
 org.keycloak.models.UserFederationSpi
+org.keycloak.storage.StorageProviderSpi
+org.keycloak.storage.federated.UserFederatedStorageProviderSpi
 org.keycloak.mappers.UserFederationMapperSpi
 org.keycloak.models.RealmSpi
 org.keycloak.models.UserSessionSpi
diff --git a/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java b/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java
index 5feb31c..7d19e83 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java
@@ -497,7 +497,7 @@ public class ResourceServerService {
             umaProtectionRole = client.addRole(Constants.AUTHZ_UMA_PROTECTION);
         }
 
-        UserModel serviceAccount = this.session.users().getUserByServiceAccountClient(client);
+        UserModel serviceAccount = this.session.users().getServiceAccount(client);
 
         if (!serviceAccount.hasRole(umaProtectionRole)) {
             serviceAccount.grantRole(umaProtectionRole);
diff --git a/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java b/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java
index 9227854..5ddd431 100644
--- a/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java
+++ b/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java
@@ -139,10 +139,10 @@ public class KeycloakIdentity implements Identity {
 
         if (this.accessToken.getClientSession() != null) {
             ClientSessionModel clientSession = this.keycloakSession.sessions().getClientSession(this.accessToken.getClientSession());
-            clientUser = this.keycloakSession.users().getUserByServiceAccountClient(clientSession.getClient());
+            clientUser = this.keycloakSession.users().getServiceAccount(clientSession.getClient());
         } else if (this.accessToken.getIssuedFor() != null) {
             ClientModel clientModel = this.keycloakSession.realms().getClientById(this.accessToken.getIssuedFor(), this.realm);
-            clientUser = this.keycloakSession.users().getUserByServiceAccountClient(clientModel);
+            clientUser = this.keycloakSession.users().getServiceAccount(clientModel);
         }
 
 
diff --git a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
index cf0c5ca..01f1186 100755
--- a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
+++ b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
@@ -319,7 +319,7 @@ public class ExportUtils {
         userRep.setFederationLink(user.getFederationLink());
 
         // Grants
-        List<UserConsentModel> consents = user.getConsents();
+        List<UserConsentModel> consents = session.users().getConsents(realm, user);
         LinkedList<UserConsentRepresentation> consentReps = new LinkedList<UserConsentRepresentation>();
         for (UserConsentModel consent : consents) {
             UserConsentRepresentation consentRep = ModelToRepresentation.toRepresentation(consent);
diff --git a/services/src/main/java/org/keycloak/forms/account/freemarker/model/ApplicationsBean.java b/services/src/main/java/org/keycloak/forms/account/freemarker/model/ApplicationsBean.java
index 0b8ec67..f845903 100755
--- a/services/src/main/java/org/keycloak/forms/account/freemarker/model/ApplicationsBean.java
+++ b/services/src/main/java/org/keycloak/forms/account/freemarker/model/ApplicationsBean.java
@@ -64,7 +64,7 @@ public class ApplicationsBean {
             MultivaluedHashMap<String, ClientRoleEntry> resourceRolesGranted = new MultivaluedHashMap<String, ClientRoleEntry>();
             List<String> claimsGranted = new LinkedList<String>();
             if (client.isConsentRequired()) {
-                UserConsentModel consent = user.getConsentByClient(client.getId());
+                UserConsentModel consent = session.users().getConsentByClient(realm, user, client.getId());
 
                 if (consent != null) {
                     processRoles(consent.getGrantedRoles(), realmRolesGranted, resourceRolesGranted);
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
index 4f17cdd..f7d68dc 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
@@ -420,13 +420,13 @@ public class TokenEndpoint {
             throw new ErrorResponseException("unauthorized_client", "Client not enabled to retrieve service account", Response.Status.UNAUTHORIZED);
         }
 
-        UserModel clientUser = session.users().getUserByServiceAccountClient(client);
+        UserModel clientUser = session.users().getServiceAccount(client);
 
         if (clientUser == null || client.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, ServiceAccountConstants.CLIENT_ID_PROTOCOL_MAPPER) == null) {
             // May need to handle bootstrap here as well
             logger.debugf("Service account user for client '%s' not found or default protocol mapper for service account not found. Creating now", client.getClientId());
             new ClientManager(new RealmManager(session)).enableServiceAccount(client);
-            clientUser = session.users().getUserByServiceAccountClient(client);
+            clientUser = session.users().getServiceAccount(client);
         }
 
         String clientUsername = clientUser.getUsername();
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
index 22f84d4..8eb2a56 100644
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
@@ -22,6 +22,8 @@ import org.keycloak.models.cache.CacheUserProvider;
 import org.keycloak.provider.Provider;
 import org.keycloak.provider.ProviderFactory;
 import org.keycloak.scripting.ScriptingProvider;
+import org.keycloak.storage.UserStorageManager;
+import org.keycloak.storage.federated.UserFederatedStorageProvider;
 
 import java.util.*;
 
@@ -36,9 +38,11 @@ public class DefaultKeycloakSession implements KeycloakSession {
     private final DefaultKeycloakTransactionManager transactionManager;
     private RealmProvider model;
     private UserProvider userModel;
+    private UserStorageManager userStorageManager;
     private ScriptingProvider scriptingProvider;
     private UserSessionProvider sessionProvider;
     private UserFederationManager federationManager;
+    private UserFederatedStorageProvider userFederatedStorageProvider;
     private KeycloakContext context;
 
     public DefaultKeycloakSession(DefaultKeycloakSessionFactory factory) {
@@ -87,6 +91,25 @@ public class DefaultKeycloakSession implements KeycloakSession {
     }
 
     @Override
+    public UserFederatedStorageProvider userFederatedStorage() {
+        if (userFederatedStorageProvider == null) {
+            userFederatedStorageProvider = getProvider(UserFederatedStorageProvider.class);
+        }
+        return userFederatedStorageProvider;
+    }
+
+    @Override
+    public UserProvider userLocalStorage() {
+        return getProvider(UserProvider.class);
+    }
+
+    @Override
+    public UserProvider userStorageManager() {
+        if (userStorageManager == null) userStorageManager = new UserStorageManager(this);
+        return userStorageManager;
+    }
+
+    @Override
     public UserProvider userStorage() {
         if (userModel == null) {
             userModel = getUserProvider();
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/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index 32f249f..e210626 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -457,7 +457,7 @@ public class AuthenticationManager {
 
         if (client.isConsentRequired()) {
 
-            UserConsentModel grantedConsent = user.getConsentByClient(client.getId());
+            UserConsentModel grantedConsent = session.users().getConsentByClient(realm, user, client.getId());
 
             ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession);
             for (RoleModel r : accessCode.getRequestedRoles()) {
@@ -511,7 +511,7 @@ public class AuthenticationManager {
 
         if (client.isConsentRequired()) {
 
-            UserConsentModel grantedConsent = user.getConsentByClient(client.getId());
+            UserConsentModel grantedConsent = session.users().getConsentByClient(realm, user, client.getId());
 
             List<RoleModel> realmRoles = new LinkedList<>();
             MultivaluedMap<String, RoleModel> resourceRoles = new MultivaluedMapImpl<>();
diff --git a/services/src/main/java/org/keycloak/services/managers/ClientManager.java b/services/src/main/java/org/keycloak/services/managers/ClientManager.java
index 607123e..7bdf5b1 100644
--- a/services/src/main/java/org/keycloak/services/managers/ClientManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ClientManager.java
@@ -103,7 +103,7 @@ public class ClientManager {
                 sessionsPersister.onClientRemoved(realm, client);
             }
 
-            UserModel serviceAccountUser = realmManager.getSession().users().getUserByServiceAccountClient(client);
+            UserModel serviceAccountUser = realmManager.getSession().users().getServiceAccount(client);
             if (serviceAccountUser != null) {
                 new UserManager(realmManager.getSession()).removeUser(realm, serviceAccountUser);
             }
@@ -150,7 +150,7 @@ public class ClientManager {
         client.setServiceAccountsEnabled(true);
 
         // Add dedicated user for this service account
-        if (realmManager.getSession().users().getUserByServiceAccountClient(client) == null) {
+        if (realmManager.getSession().users().getServiceAccount(client) == null) {
             String username = ServiceAccountConstants.SERVICE_ACCOUNT_USER_PREFIX + client.getClientId();
             logger.debugf("Creating service account user '%s'", username);
 
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index 4907707..251cd8e 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -500,7 +500,7 @@ public class AccountService extends AbstractSecuredLocalService {
 
         // Revoke grant in UserModel
         UserModel user = auth.getUser();
-        user.revokeConsentForClient(client.getId());
+        session.users().revokeConsentForClient(realm, user, client.getId());
         new UserSessionManager(session).revokeOfflineToken(user, client);
 
         // Logout clientSessions for this user and client
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
index 18023e8..94d4821 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
@@ -315,11 +315,11 @@ public class ClientResource {
             throw new NotFoundException("Could not find client");
         }
 
-        UserModel user = session.users().getUserByServiceAccountClient(client);
+        UserModel user = session.users().getServiceAccount(client);
         if (user == null) {
             if (client.isServiceAccountsEnabled()) {
                 new ClientManager(new RealmManager(session)).enableServiceAccount(client);
-                user = session.users().getUserByServiceAccountClient(client);
+                user = session.users().getServiceAccount(client);
             } else {
                 throw new BadRequestException("Service account not enabled for the client '" + client.getClientId() + "'");
             }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index 1b4586b..5788b42 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -523,7 +523,7 @@ public class UsersResource {
         Set<ClientModel> offlineClients = new UserSessionManager(session).findClientsWithOfflineToken(realm, user);
 
         for (ClientModel client : realm.getClients()) {
-            UserConsentModel consent = user.getConsentByClient(client.getId());
+            UserConsentModel consent = session.users().getConsentByClient(realm, user, client.getId());
             boolean hasOfflineToken = offlineClients.contains(client);
 
             if (consent == null && !hasOfflineToken) {
@@ -572,7 +572,7 @@ public class UsersResource {
         }
 
         ClientModel client = realm.getClientByClientId(clientId);
-        boolean revokedConsent = user.revokeConsentForClient(client.getId());
+        boolean revokedConsent = session.users().revokeConsentForClient(realm, user, client.getId());
         boolean revokedOfflineToken = new UserSessionManager(session).revokeOfflineToken(user, client);
 
         if (revokedConsent) {
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index fab5815..cf8791e 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -667,10 +667,10 @@ public class LoginActionsService {
             return response;
         }
 
-        UserConsentModel grantedConsent = user.getConsentByClient(client.getId());
+        UserConsentModel grantedConsent = session.users().getConsentByClient(realm, user, client.getId());
         if (grantedConsent == null) {
             grantedConsent = new UserConsentModel(client);
-            user.addConsent(grantedConsent);
+            session.users().addConsent(realm, user, grantedConsent);
         }
         for (RoleModel role : accessCode.getRequestedRoles()) {
             grantedConsent.addGrantedRole(role);
@@ -680,7 +680,7 @@ public class LoginActionsService {
                 grantedConsent.addGrantedProtocolMapper(protocolMapper);
             }
         }
-        user.updateConsent(grantedConsent);
+        session.users().updateConsent(realm, user, grantedConsent);
 
         event.detail(Details.CONSENT, Details.CONSENT_VALUE_CONSENT_GRANTED);
         event.success();
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/java/org/keycloak/testsuite/model/ImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
index 8beaffb..dedc8d1 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
@@ -357,15 +357,15 @@ public class ImportTest extends AbstractModelTest {
 
         // Test user consents
         admin =  session.users().getUserByUsername("admin", realm);
-        Assert.assertEquals(2, admin.getConsents().size());
+        Assert.assertEquals(2, session.users().getConsents(realm, admin).size());
 
-        UserConsentModel appAdminConsent = admin.getConsentByClient(application.getId());
+        UserConsentModel appAdminConsent = session.users().getConsentByClient(realm, admin, application.getId());
         Assert.assertEquals(2, appAdminConsent.getGrantedRoles().size());
         Assert.assertTrue(appAdminConsent.getGrantedProtocolMappers() == null || appAdminConsent.getGrantedProtocolMappers().isEmpty());
         Assert.assertTrue(appAdminConsent.isRoleGranted(realm.getRole("admin")));
         Assert.assertTrue(appAdminConsent.isRoleGranted(application.getRole("app-admin")));
 
-        UserConsentModel otherAppAdminConsent = admin.getConsentByClient(otherApp.getId());
+        UserConsentModel otherAppAdminConsent = session.users().getConsentByClient(realm, admin, otherApp.getId());
         Assert.assertEquals(1, otherAppAdminConsent.getGrantedRoles().size());
         Assert.assertEquals(1, otherAppAdminConsent.getGrantedProtocolMappers().size());
         Assert.assertTrue(otherAppAdminConsent.isRoleGranted(realm.getRole("admin")));
@@ -382,8 +382,8 @@ public class ImportTest extends AbstractModelTest {
         // Test service accounts
         Assert.assertFalse(application.isServiceAccountsEnabled());
         Assert.assertTrue(otherApp.isServiceAccountsEnabled());
-        Assert.assertNull(session.users().getUserByServiceAccountClient(application));
-        UserModel linked = session.users().getUserByServiceAccountClient(otherApp);
+        Assert.assertNull(session.users().getServiceAccount(application));
+        UserModel linked = session.users().getServiceAccount(otherApp);
         Assert.assertNotNull(linked);
         Assert.assertEquals("my-service-user", linked.getUsername());
     }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserConsentModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserConsentModelTest.java
index 0a03398..2f71b23 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserConsentModelTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserConsentModelTest.java
@@ -67,7 +67,7 @@ public class UserConsentModelTest extends AbstractModelTest {
         johnFooGrant.addGrantedRole(realmRole);
         johnFooGrant.addGrantedRole(barClientRole);
         johnFooGrant.addGrantedProtocolMapper(fooMapper);
-        john.addConsent(johnFooGrant);
+        realmManager.getSession().users().addConsent(realm, john, johnFooGrant);
 
         UserConsentModel johnBarGrant = new UserConsentModel(barClient);
         johnBarGrant.addGrantedProtocolMapper(barMapper);
@@ -75,17 +75,17 @@ public class UserConsentModelTest extends AbstractModelTest {
 
         // Update should fail as grant doesn't yet exists
         try {
-            john.updateConsent(johnBarGrant);
+            realmManager.getSession().users().updateConsent(realm, john, johnBarGrant);
             Assert.fail("Not expected to end here");
         } catch (ModelException expected) {
         }
 
-        john.addConsent(johnBarGrant);
+        realmManager.getSession().users().addConsent(realm, john, johnBarGrant);
 
         UserConsentModel maryFooGrant = new UserConsentModel(fooClient);
         maryFooGrant.addGrantedRole(realmRole);
         maryFooGrant.addGrantedProtocolMapper(fooMapper);
-        mary.addConsent(maryFooGrant);
+        realmManager.getSession().users().addConsent(realm, mary, maryFooGrant);
 
         commit();
     }
@@ -99,27 +99,27 @@ public class UserConsentModelTest extends AbstractModelTest {
         UserModel john = session.users().getUserByUsername("john", realm);
         UserModel mary = session.users().getUserByUsername("mary", realm);
 
-        UserConsentModel johnFooConsent = john.getConsentByClient(fooClient.getId());
+        UserConsentModel johnFooConsent = realmManager.getSession().users().getConsentByClient(realm, john, fooClient.getId());
         Assert.assertEquals(johnFooConsent.getGrantedRoles().size(), 2);
         Assert.assertEquals(johnFooConsent.getGrantedProtocolMappers().size(), 1);
         Assert.assertTrue(isRoleGranted(realm, "realm-role", johnFooConsent));
         Assert.assertTrue(isRoleGranted(barClient, "bar-client-role", johnFooConsent));
         Assert.assertTrue(isMapperGranted(fooClient, "foo", johnFooConsent));
 
-        UserConsentModel johnBarConsent = john.getConsentByClient(barClient.getId());
+        UserConsentModel johnBarConsent = realmManager.getSession().users().getConsentByClient(realm, john, barClient.getId());
         Assert.assertEquals(johnBarConsent.getGrantedRoles().size(), 1);
         Assert.assertEquals(johnBarConsent.getGrantedProtocolMappers().size(), 1);
         Assert.assertTrue(isRoleGranted(realm, "realm-role", johnBarConsent));
         Assert.assertTrue(isMapperGranted(barClient, "bar", johnBarConsent));
 
-        UserConsentModel maryConsent = mary.getConsentByClient(fooClient.getId());
+        UserConsentModel maryConsent = realmManager.getSession().users().getConsentByClient(realm, mary, fooClient.getId());
         Assert.assertEquals(maryConsent.getGrantedRoles().size(), 1);
         Assert.assertEquals(maryConsent.getGrantedProtocolMappers().size(), 1);
         Assert.assertTrue(isRoleGranted(realm, "realm-role", maryConsent));
         Assert.assertFalse(isRoleGranted(barClient, "bar-client-role", maryConsent));
         Assert.assertTrue(isMapperGranted(fooClient, "foo", maryConsent));
 
-        Assert.assertNull(mary.getConsentByClient(barClient.getId()));
+        Assert.assertNull(realmManager.getSession().users().getConsentByClient(realm, mary, barClient.getId()));
     }
 
     @Test
@@ -130,10 +130,10 @@ public class UserConsentModelTest extends AbstractModelTest {
         UserModel john = session.users().getUserByUsername("john", realm);
         UserModel mary = session.users().getUserByUsername("mary", realm);
 
-        List<UserConsentModel> johnConsents = john.getConsents();
+        List<UserConsentModel> johnConsents = realmManager.getSession().users().getConsents(realm, john);
         Assert.assertEquals(2, johnConsents.size());
 
-        List<UserConsentModel> maryConsents = mary.getConsents();
+        List<UserConsentModel> maryConsents = realmManager.getSession().users().getConsents(realm, mary);
         Assert.assertEquals(1, maryConsents.size());
         UserConsentModel maryConsent = maryConsents.get(0);
         Assert.assertEquals(maryConsent.getClient().getId(), fooClient.getId());
@@ -149,7 +149,7 @@ public class UserConsentModelTest extends AbstractModelTest {
         ClientModel fooClient = realm.getClientByClientId("foo-client");
         UserModel john = session.users().getUserByUsername("john", realm);
 
-        UserConsentModel johnConsent = john.getConsentByClient(fooClient.getId());
+        UserConsentModel johnConsent = realmManager.getSession().users().getConsentByClient(realm, john, fooClient.getId());
 
         // Remove foo protocol mapper from johnConsent
         ProtocolMapperModel protMapperModel = fooClient.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, "foo");
@@ -162,14 +162,14 @@ public class UserConsentModelTest extends AbstractModelTest {
         RoleModel newRealmRole = realm.addRole("new-realm-role");
         johnConsent.addGrantedRole(newRealmRole);
 
-        john.updateConsent(johnConsent);
+        realmManager.getSession().users().updateConsent(realm, john, johnConsent);
 
         commit();
 
         realm = realmManager.getRealm("original");
         fooClient = realm.getClientByClientId("foo-client");
         john = session.users().getUserByUsername("john", realm);
-        johnConsent = john.getConsentByClient(fooClient.getId());
+        johnConsent = realmManager.getSession().users().getConsentByClient(realm, john, fooClient.getId());
 
         Assert.assertEquals(johnConsent.getGrantedRoles().size(), 2);
         Assert.assertEquals(johnConsent.getGrantedProtocolMappers().size(), 0);
@@ -184,13 +184,13 @@ public class UserConsentModelTest extends AbstractModelTest {
         ClientModel fooClient = realm.getClientByClientId("foo-client");
         UserModel john = session.users().getUserByUsername("john", realm);
 
-        john.revokeConsentForClient(fooClient.getId());
+        realmManager.getSession().users().revokeConsentForClient(realm, john, fooClient.getId());
 
         commit();
 
         realm = realmManager.getRealm("original");
         john = session.users().getUserByUsername("john", realm);
-        Assert.assertNull(john.getConsentByClient(fooClient.getId()));
+        Assert.assertNull(realmManager.getSession().users().getConsentByClient(realm, john, fooClient.getId()));
     }
 
     @Test
@@ -213,7 +213,7 @@ public class UserConsentModelTest extends AbstractModelTest {
         realm = realmManager.getRealm("original");
         fooClient = realm.getClientByClientId("foo-client");
         UserModel john = session.users().getUserByUsername("john", realm);
-        UserConsentModel johnConsent = john.getConsentByClient(fooClient.getId());
+        UserConsentModel johnConsent = realmManager.getSession().users().getConsentByClient(realm, john, fooClient.getId());
 
         Assert.assertEquals(johnConsent.getGrantedRoles().size(), 2);
         Assert.assertEquals(johnConsent.getGrantedProtocolMappers().size(), 0);
@@ -232,7 +232,7 @@ public class UserConsentModelTest extends AbstractModelTest {
         ClientModel fooClient = realm.getClientByClientId("foo-client");
         ClientModel barClient = realm.getClientByClientId("bar-client");
         UserModel john = session.users().getUserByUsername("john", realm);
-        UserConsentModel johnConsent = john.getConsentByClient(fooClient.getId());
+        UserConsentModel johnConsent = realmManager.getSession().users().getConsentByClient(realm, john, fooClient.getId());
 
         Assert.assertEquals(johnConsent.getGrantedRoles().size(), 1);
         Assert.assertEquals(johnConsent.getGrantedProtocolMappers().size(), 1);
@@ -254,13 +254,13 @@ public class UserConsentModelTest extends AbstractModelTest {
 
         UserModel john = session.users().getUserByUsername("john", realm);
 
-        UserConsentModel johnFooConsent = john.getConsentByClient(fooClient.getId());
+        UserConsentModel johnFooConsent = realmManager.getSession().users().getConsentByClient(realm, john, fooClient.getId());
         Assert.assertEquals(johnFooConsent.getGrantedRoles().size(), 1);
         Assert.assertEquals(johnFooConsent.getGrantedProtocolMappers().size(), 1);
         Assert.assertTrue(isRoleGranted(realm, "realm-role", johnFooConsent));
         Assert.assertTrue(isMapperGranted(fooClient, "foo", johnFooConsent));
 
-        Assert.assertNull(john.getConsentByClient(barClient.getId()));
+        Assert.assertNull(realmManager.getSession().users().getConsentByClient(realm, john, barClient.getId()));
     }
 
     private boolean isRoleGranted(RoleContainerModel roleContainer, String roleName, UserConsentModel consentModel) {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java
index 0841c1f..83ea5ad 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java
@@ -271,7 +271,7 @@ public class UserModelTest extends AbstractModelTest {
         user2.setLastName("Doe");
 
         // Search
-        Assert.assertNull(session.users().getUserByServiceAccountClient(client));
+        Assert.assertNull(session.users().getServiceAccount(client));
         List<UserModel> users = session.users().searchForUser("John Doe", realm);
         Assert.assertEquals(2, users.size());
         Assert.assertTrue(users.contains(user1));
@@ -284,7 +284,7 @@ public class UserModelTest extends AbstractModelTest {
 
         // Search and assert service account user not found
         realm = realmManager.getRealmByName("original");
-        UserModel searched = session.users().getUserByServiceAccountClient(client);
+        UserModel searched = session.users().getServiceAccount(client);
         Assert.assertEquals(searched, user1);
         users = session.users().searchForUser("John Doe", realm);
         Assert.assertEquals(1, users.size());
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
index e484645..bbc9f27 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
@@ -635,7 +635,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
     public UserRepresentation getUserByServiceAccountClient(@QueryParam("realmName") String realmName, @QueryParam("clientId") String clientId) {
         RealmModel realm = getRealmByName(realmName);
         ClientModel client =  realm.getClientByClientId(clientId);
-        UserModel user = session.users().getUserByServiceAccountClient(client);
+        UserModel user = session.users().getServiceAccount(client);
         if (user == null) return null;
         return ModelToRepresentation.toRepresentation(user);
     }
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}"
     },