keycloak-uncached

Changes

testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/model/CompositeRolesModelTest.java 158(+0 -158)

testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/model/ConcurrentTransactionsTest.java 234(+0 -234)

testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/model/MultipleRealmsTest.java 122(+0 -122)

testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/model/UserConsentModelTest.java 286(+0 -286)

testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/model/UserConsentWithUserStorageModelTest.java 296(+0 -296)

testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/model/UserModelTest.java 442(+0 -442)

testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/model/UserSessionPersisterProviderTest.java 469(+0 -469)

testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/model/UserSessionProviderOfflineTest.java 467(+0 -467)

Details

diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/ConcurrentTransactionsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/ConcurrentTransactionsTest.java
new file mode 100755
index 0000000..ebe9285
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/ConcurrentTransactionsTest.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.model;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.TargetsContainer;
+import org.jboss.logging.Logger;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.models.*;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
+import org.keycloak.testsuite.arquillian.annotation.ModelTest;
+import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
+
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.keycloak.testsuite.arquillian.DeploymentTargetModifier.AUTH_SERVER_CURRENT;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ConcurrentTransactionsTest extends AbstractTestRealmKeycloakTest {
+
+    @Deployment
+    @TargetsContainer(AUTH_SERVER_CURRENT)
+    public static WebArchive deploy() {
+        return RunOnServerDeployment.create(UserResource.class, UserSessionProviderOfflineTest.class)
+                .addPackages(true,
+                        "org.keycloak.testsuite",
+                        "org.keycloak.testsuite.model");
+    }
+
+    private static final Logger logger = Logger.getLogger(ConcurrentTransactionsTest.class);
+
+    @Test
+    @ModelTest
+    public void persistClient(KeycloakSession session) {
+
+        final ClientModel[] client = {null};
+        AtomicReference<String> clientDBIdAtomic = new AtomicReference<>();
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionSetup) -> {
+
+            RealmModel realm = sessionSetup.realms().getRealm("test");
+            sessionSetup.users().addUser(realm, "user1").setEmail("user1@localhost");
+            sessionSetup.users().addUser(realm, "user2").setEmail("user2@localhost");
+
+            realm = sessionSetup.realms().createRealm("original");
+
+            client[0] = sessionSetup.realms().addClient(realm, "client");
+            client[0].setSecret("old");
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession session1) -> {
+            String clientDBId = client[0].getId();
+            clientDBIdAtomic.set(clientDBId);
+
+            final KeycloakSessionFactory sessionFactory = session1.getKeycloakSessionFactory();
+
+            final CountDownLatch transactionsCounter = new CountDownLatch(2);
+            final CountDownLatch readLatch = new CountDownLatch(1);
+            final CountDownLatch updateLatch = new CountDownLatch(1);
+
+            Thread thread1 = new Thread(() -> {
+                KeycloakModelUtils.runJobInTransaction(sessionFactory, session11 -> {
+                    try {
+                        KeycloakSession currentSession = session11;
+                        // Wait until transaction in both threads started
+                        transactionsCounter.countDown();
+                        logger.info("transaction1 started");
+                        transactionsCounter.await();
+
+                        // Read client
+                        RealmModel realm1 = currentSession.realms().getRealmByName("original");
+                        ClientModel client1 = currentSession.realms().getClientByClientId("client", realm1);
+                        logger.info("transaction1: Read client finished");
+                        readLatch.countDown();
+
+                        // Wait until thread2 updates client and commits
+                        updateLatch.await();
+                        logger.info("transaction1: Going to read client again");
+
+                        client1 = currentSession.realms().getClientByClientId("client", realm1);
+                        logger.info("transaction1: secret: " + client1.getSecret());
+
+                    } catch (Exception e) {
+                        throw new RuntimeException(e);
+                    }
+                });
+            });
+
+            Thread thread2 = new Thread(() -> {
+                KeycloakModelUtils.runJobInTransaction(sessionFactory, session22 -> {
+                    try {
+                        KeycloakSession currentSession = session22;
+                        // Wait until transaction in both threads started
+                        transactionsCounter.countDown();
+                        logger.info("transaction2 started");
+                        transactionsCounter.await();
+
+                        readLatch.await();
+                        logger.info("transaction2: Going to update client secret");
+
+                        RealmModel realm12 = currentSession.realms().getRealmByName("original");
+                        ClientModel client12 = currentSession.realms().getClientByClientId("client", realm12);
+                        client12.setSecret("new");
+                    } catch (Exception e) {
+                        throw new RuntimeException(e);
+                    }
+                });
+                logger.info("transaction2: commited");
+                updateLatch.countDown();
+            });
+
+            thread1.start();
+            thread2.start();
+
+            try {
+                thread1.join();
+                thread2.join();
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+
+            logger.info("after thread join");
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession session2) -> {
+            RealmModel realm = session2.realms().getRealmByName("original");
+            String clientDBId = clientDBIdAtomic.get();
+
+            ClientModel clientFromCache = session2.realms().getClientById(clientDBId, realm);
+            ClientModel clientFromDB = session2.getProvider(RealmProvider.class).getClientById(clientDBId, realm);
+
+            logger.info("SECRET FROM DB : " + clientFromDB.getSecret());
+            logger.info("SECRET FROM CACHE : " + clientFromCache.getSecret());
+
+            Assert.assertEquals("new", clientFromDB.getSecret());
+            Assert.assertEquals("new", clientFromCache.getSecret());
+
+            session2.sessions().removeUserSessions(realm);
+
+            tearDownRealm(session2, "user1", "user2");
+        });
+    }
+
+
+    // KEYCLOAK-3296 , KEYCLOAK-3494
+    @Test
+    @ModelTest
+    public void removeUserAttribute(KeycloakSession session) throws Exception {
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionSet) -> {
+
+            RealmModel realm = sessionSet.realms().createRealm("original");
+
+            UserModel john = sessionSet.users().addUser(realm, "john");
+            john.setSingleAttribute("foo", "val1");
+
+            UserModel john2 = sessionSet.users().addUser(realm, "john2");
+            john2.setAttribute("foo", Arrays.asList("val1", "val2"));
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession session2) -> {
+
+            final KeycloakSessionFactory sessionFactory = session2.getKeycloakSessionFactory();
+
+            AtomicReference<Exception> reference = new AtomicReference<>();
+
+            final CountDownLatch readAttrLatch = new CountDownLatch(2);
+
+            Runnable runnable = () -> {
+                try {
+                    KeycloakModelUtils.runJobInTransaction(sessionFactory, session1 -> {
+                        try {
+                            // Read user attribute
+                            RealmModel realm = session1.realms().getRealmByName("original");
+                            UserModel john = session1.users().getUserByUsername("john", realm);
+                            String attrVal = john.getFirstAttribute("foo");
+
+                            UserModel john2 = session1.users().getUserByUsername("john2", realm);
+                            String attrVal2 = john2.getFirstAttribute("foo");
+
+                            // Wait until it's read in both threads
+                            readAttrLatch.countDown();
+                            readAttrLatch.await();
+
+                            // KEYCLOAK-3296 : Remove user attribute in both threads
+                            john.removeAttribute("foo");
+
+                            // KEYCLOAK-3494 : Set single attribute in both threads
+                            john2.setSingleAttribute("foo", "bar");
+                        } catch (Exception e) {
+                            throw new RuntimeException(e);
+                        }
+                    });
+                } catch (Exception e) {
+                    reference.set(e);
+                    throw new RuntimeException(e);
+                } finally {
+                    readAttrLatch.countDown();
+                }
+            };
+
+            Thread thread1 = new Thread(runnable);
+            Thread thread2 = new Thread(runnable);
+
+            thread1.start();
+            thread2.start();
+
+            try {
+                thread1.join();
+                thread2.join();
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+
+            logger.info("removeUserAttribute: after thread join");
+            if (reference.get() != null) {
+                Assert.fail("Exception happened in some of threads. Details: " + reference.get().getMessage());
+            }
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionTearDown) -> {
+            tearDownRealm(sessionTearDown, "john", "john2");
+        });
+    }
+
+    private void tearDownRealm(KeycloakSession session, String user1, String user2) {
+        KeycloakSession currentSession = session;
+
+        RealmModel realm = currentSession.realms().getRealmByName("original");
+
+        UserModel realmUser1 = currentSession.users().getUserByUsername(user1, realm);
+        UserModel realmUser2 = currentSession.users().getUserByUsername(user2, realm);
+
+        UserManager um = new UserManager(currentSession);
+        if (realmUser1 != null) {
+            um.removeUser(realm, realmUser1);
+        }
+        if (realmUser2 != null) {
+            um.removeUser(realm, realmUser2);
+        }
+
+        assert (currentSession.realms().removeRealm(realm.getId()));
+    }
+
+    @Override
+    public void configureTestRealm(RealmRepresentation testRealm) {
+    }
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/ImportTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/ImportTest.java
index 1e84d27..85da96c 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/ImportTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/ImportTest.java
@@ -57,7 +57,7 @@ public class ImportTest extends AbstractTestRealmKeycloakTest {
     @Deployment
     @TargetsContainer(AUTH_SERVER_CURRENT)
     public static WebArchive deploy() {
-        return RunOnServerDeployment.create(UserResource.class, AbstractLDAPTest.class)
+        return RunOnServerDeployment.create(UserResource.class, ImportTest.class)
                 .addPackages(true,
                         "org.keycloak.testsuite",
                         "org.keycloak.testsuite.model");
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/MultipleRealmsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/MultipleRealmsTest.java
new file mode 100755
index 0000000..bb0b710
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/MultipleRealmsTest.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.model;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.TargetsContainer;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.models.*;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
+import org.keycloak.testsuite.arquillian.annotation.ModelTest;
+import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.keycloak.testsuite.arquillian.DeploymentTargetModifier.AUTH_SERVER_CURRENT;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class MultipleRealmsTest extends AbstractTestRealmKeycloakTest {
+
+    @Deployment
+    @TargetsContainer(AUTH_SERVER_CURRENT)
+    public static WebArchive deploy() {
+        return RunOnServerDeployment.create(UserResource.class, MultipleRealmsTest.class)
+                .addPackages(true,
+                        "org.keycloak.testsuite",
+                        "org.keycloak.testsuite.model");
+    }
+
+    public static void createObjects(KeycloakSession session, RealmModel realm) {
+        ClientModel app1 = realm.addClient("app1");
+        realm.addClient("app2");
+
+        session.users().addUser(realm, "user1");
+        session.users().addUser(realm, "user2");
+
+        realm.addRole("role1");
+        realm.addRole("role2");
+
+        app1.addRole("app1Role1");
+        app1.addScopeMapping(realm.getRole("role1"));
+
+        realm.addClient("cl1");
+    }
+
+    @Test
+    @ModelTest
+    public void testUsers(KeycloakSession session) {
+
+        AtomicReference<UserModel> r1user1Atomic = new AtomicReference<>();
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionTestUser1) -> {
+            KeycloakSession currentSession = sessionTestUser1;
+
+            RealmModel realm1 = currentSession.realms().createRealm("id1", "realm1");
+            RealmModel realm2 = currentSession.realms().createRealm("id2", "realm2");
+
+            createObjects(currentSession, realm1);
+            createObjects(currentSession, realm2);
+
+            UserModel r1user1 = currentSession.users().getUserByUsername("user1", realm1);
+            UserModel r2user1 = currentSession.users().getUserByUsername("user1", realm2);
+
+            r1user1Atomic.set(r1user1);
+
+            Assert.assertEquals(r1user1.getUsername(), r2user1.getUsername());
+            Assert.assertNotEquals(r1user1.getId(), r2user1.getId());
+
+            // Test password
+            currentSession.userCredentialManager().updateCredential(realm1, r1user1, UserCredentialModel.password("pass1"));
+            currentSession.userCredentialManager().updateCredential(realm2, r2user1, UserCredentialModel.password("pass2"));
+
+            Assert.assertTrue(currentSession.userCredentialManager().isValid(realm1, r1user1, UserCredentialModel.password("pass1")));
+            Assert.assertFalse(currentSession.userCredentialManager().isValid(realm1, r1user1, UserCredentialModel.password("pass2")));
+            Assert.assertFalse(currentSession.userCredentialManager().isValid(realm2, r2user1, UserCredentialModel.password("pass1")));
+            Assert.assertTrue(currentSession.userCredentialManager().isValid(realm2, r2user1, UserCredentialModel.password("pass2")));
+
+            // Test searching
+            Assert.assertEquals(2, currentSession.users().searchForUser("user", realm1).size());
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionTestUser2) -> {
+            KeycloakSession currentSession = sessionTestUser2;
+
+            RealmModel realm1 = currentSession.realms().getRealm("id1");
+            RealmModel realm2 = currentSession.realms().getRealm("id2");
+
+            UserModel r1user1 = r1user1Atomic.get();
+
+            currentSession.users().removeUser(realm1, r1user1);
+            UserModel user2 = currentSession.users().getUserByUsername("user2", realm1);
+            currentSession.users().removeUser(realm1, user2);
+            Assert.assertEquals(0, currentSession.users().searchForUser("user", realm1).size());
+            Assert.assertEquals(2, currentSession.users().searchForUser("user", realm2).size());
+
+
+            UserModel user1 = currentSession.users().getUserByUsername("user1", realm1);
+            UserModel user1a = currentSession.users().getUserByUsername("user1", realm2);
+
+            UserManager um = new UserManager(session);
+            if (user1 != null) {
+                um.removeUser(realm1, user1);
+            }
+            if (user1a != null) {
+                um.removeUser(realm2, user1a);
+            }
+
+            currentSession.realms().removeRealm("id1");
+            currentSession.realms().removeRealm("id2");
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void testGetById(KeycloakSession session) {
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionById) -> {
+            KeycloakSession currentSession = sessionById;
+
+            RealmModel realm1 = currentSession.realms().createRealm("id1", "realm1");
+            RealmModel realm2 = currentSession.realms().createRealm("id2", "realm2");
+
+            createObjects(currentSession, realm1);
+            createObjects(currentSession, realm2);
+
+            Assert.assertEquals(realm1, currentSession.realms().getRealm("id1"));
+            Assert.assertEquals(realm1, currentSession.realms().getRealmByName("realm1"));
+            Assert.assertEquals(realm2, currentSession.realms().getRealm("id2"));
+            Assert.assertEquals(realm2, currentSession.realms().getRealmByName("realm2"));
+
+            ClientModel r1app1 = realm1.getClientByClientId("app1");
+
+            Assert.assertNotNull(realm1.getClientByClientId("app2"));
+            Assert.assertNotNull(realm2.getClientByClientId("app1"));
+            Assert.assertNotNull(realm2.getClientByClientId("app2"));
+
+            Assert.assertEquals(r1app1, realm1.getClientById(r1app1.getId()));
+            Assert.assertNull(realm2.getClientById(r1app1.getId()));
+
+            ClientModel r2cl1 = realm2.getClientByClientId("cl1");
+            Assert.assertEquals(r2cl1.getId(), realm2.getClientById(r2cl1.getId()).getId());
+            Assert.assertNull(realm1.getClientByClientId(r2cl1.getId()));
+
+            RoleModel r1App1Role = r1app1.getRole("app1Role1");
+            Assert.assertEquals(r1App1Role, realm1.getRoleById(r1App1Role.getId()));
+            Assert.assertNull(realm2.getRoleById(r1App1Role.getId()));
+
+            RoleModel r2Role1 = realm2.getRole("role2");
+            Assert.assertNull(realm1.getRoleById(r2Role1.getId()));
+            Assert.assertEquals(r2Role1, realm2.getRoleById(r2Role1.getId()));
+
+
+            UserModel user1 = currentSession.users().getUserByUsername("user1", realm1);
+            UserModel user1a = currentSession.users().getUserByUsername("user1", realm2);
+
+            UserManager um = new UserManager(currentSession);
+            if (user1 != null) {
+                um.removeUser(realm1, user1);
+            }
+            if (user1a != null) {
+                um.removeUser(realm2, user1a);
+            }
+
+            currentSession.realms().removeRealm("id1");
+            currentSession.realms().removeRealm("id2");
+        });
+    }
+
+    @Override
+    public void configureTestRealm(RealmRepresentation testRealm) {
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserConsentModelTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserConsentModelTest.java
new file mode 100644
index 0000000..75255e1
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserConsentModelTest.java
@@ -0,0 +1,411 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.model;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.TargetsContainer;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.models.*;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.storage.client.ClientStorageProviderModel;
+import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
+import org.keycloak.testsuite.arquillian.annotation.ModelTest;
+import org.keycloak.testsuite.federation.HardcodedClientStorageProviderFactory;
+import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.keycloak.testsuite.arquillian.DeploymentTargetModifier.AUTH_SERVER_CURRENT;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UserConsentModelTest extends AbstractTestRealmKeycloakTest {
+
+    private static ComponentModel clientStorageComponent;
+
+    @Deployment
+    @TargetsContainer(AUTH_SERVER_CURRENT)
+    public static WebArchive deploy() {
+        return RunOnServerDeployment.create(UserResource.class, UserConsentModelTest.class)
+                .addPackages(true,
+                        "org.keycloak.testsuite",
+                        "org.keycloak.testsuite.model");
+    }
+
+    @Before
+    public void before() {
+        testingClient.server().run(session -> {
+            setupEnv(session);
+        });
+    }
+
+    @After
+    public void after() {
+        testingClient.server().run(session -> {
+
+            RealmManager realmManager = new RealmManager(session);
+            RealmModel realm = realmManager.getRealmByName("original");
+
+            if (realm != null) {
+
+                session.sessions().removeUserSessions(realm);
+                UserModel user = session.users().getUserByUsername("user", realm);
+                UserModel user1 = session.users().getUserByUsername("user1", realm);
+                UserModel user2 = session.users().getUserByUsername("user2", realm);
+                UserModel user3 = session.users().getUserByUsername("user3", realm);
+
+                UserManager um = new UserManager(session);
+                if (user != null) {
+                    um.removeUser(realm, user);
+                }
+                if (user1 != null) {
+                    um.removeUser(realm, user1);
+                }
+                if (user2 != null) {
+                    um.removeUser(realm, user2);
+                }
+                if (user3 != null) {
+                    um.removeUser(realm, user3);
+                }
+                realmManager.removeRealm(realm);
+            }
+        });
+    }
+
+    public static void setupEnv(KeycloakSession session) {
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionEnv) -> {
+            KeycloakSession currentSession = sessionEnv;
+
+            RealmManager realmManager = new RealmManager(currentSession);
+            RealmModel realm = realmManager.createRealm("original");
+
+            ClientModel fooClient = realm.addClient("foo-client");
+            ClientModel barClient = realm.addClient("bar-client");
+
+            ClientScopeModel fooScope = realm.addClientScope("foo");
+            fooScope.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
+
+            ClientScopeModel barScope = realm.addClientScope("bar");
+            fooScope.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
+
+            UserModel john = currentSession.users().addUser(realm, "john");
+            UserModel mary = currentSession.users().addUser(realm, "mary");
+
+            UserConsentModel johnFooGrant = new UserConsentModel(fooClient);
+            johnFooGrant.addGrantedClientScope(fooScope);
+            realmManager.getSession().users().addConsent(realm, john.getId(), johnFooGrant);
+
+            UserConsentModel johnBarGrant = new UserConsentModel(barClient);
+            johnBarGrant.addGrantedClientScope(barScope);
+
+            // Update should fail as grant doesn't yet exists
+            try {
+                realmManager.getSession().users().updateConsent(realm, john.getId(), johnBarGrant);
+                Assert.fail("Not expected to end here");
+            } catch (ModelException expected) {
+            }
+
+            realmManager.getSession().users().addConsent(realm, john.getId(), johnBarGrant);
+
+            UserConsentModel maryFooGrant = new UserConsentModel(fooClient);
+            maryFooGrant.addGrantedClientScope(fooScope);
+            realmManager.getSession().users().addConsent(realm, mary.getId(), maryFooGrant);
+
+            ClientStorageProviderModel clientStorage = new ClientStorageProviderModel();
+            clientStorage.setProviderId(HardcodedClientStorageProviderFactory.PROVIDER_ID);
+            clientStorage.getConfig().putSingle(HardcodedClientStorageProviderFactory.CLIENT_ID, "hardcoded-client");
+            clientStorage.getConfig().putSingle(HardcodedClientStorageProviderFactory.REDIRECT_URI, "http://localhost:8081/*");
+            clientStorage.getConfig().putSingle(HardcodedClientStorageProviderFactory.CONSENT, "true");
+            clientStorage.setParentId(realm.getId());
+            clientStorageComponent = realm.addComponentModel(clientStorage);
+
+            ClientModel hardcodedClient = currentSession.realms().getClientByClientId("hardcoded-client", realm);
+
+            Assert.assertNotNull(hardcodedClient);
+
+            UserConsentModel maryHardcodedGrant = new UserConsentModel(hardcodedClient);
+            realmManager.getSession().users().addConsent(realm, mary.getId(), maryHardcodedGrant);
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void basicConsentTest(KeycloakSession session) {
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionCT) -> {
+            KeycloakSession currentSession = sessionCT;
+
+            RealmModel realm = currentSession.realms().getRealm("original");
+
+            ClientModel fooClient = realm.getClientByClientId("foo-client");
+            ClientModel barClient = realm.getClientByClientId("bar-client");
+
+            UserModel john = currentSession.users().getUserByUsername("john", realm);
+            UserModel mary = currentSession.users().getUserByUsername("mary", realm);
+
+            UserConsentModel johnFooConsent = currentSession.users().getConsentByClient(realm, john.getId(), fooClient.getId());
+            Assert.assertEquals(johnFooConsent.getGrantedClientScopes().size(), 1);
+            Assert.assertTrue(isClientScopeGranted(realm, "foo", johnFooConsent));
+            Assert.assertNotNull("Created Date should be set", johnFooConsent.getCreatedDate());
+            Assert.assertNotNull("Last Updated Date should be set", johnFooConsent.getLastUpdatedDate());
+
+            UserConsentModel johnBarConsent = currentSession.users().getConsentByClient(realm, john.getId(), barClient.getId());
+            Assert.assertEquals(johnBarConsent.getGrantedClientScopes().size(), 1);
+            Assert.assertTrue(isClientScopeGranted(realm, "bar", johnBarConsent));
+            Assert.assertNotNull("Created Date should be set", johnBarConsent.getCreatedDate());
+            Assert.assertNotNull("Last Updated Date should be set", johnBarConsent.getLastUpdatedDate());
+
+            UserConsentModel maryConsent = currentSession.users().getConsentByClient(realm, mary.getId(), fooClient.getId());
+            Assert.assertEquals(maryConsent.getGrantedClientScopes().size(), 1);
+            Assert.assertTrue(isClientScopeGranted(realm, "foo", maryConsent));
+            Assert.assertNotNull("Created Date should be set", maryConsent.getCreatedDate());
+            Assert.assertNotNull("Last Updated Date should be set", maryConsent.getLastUpdatedDate());
+
+            ClientModel hardcodedClient = currentSession.realms().getClientByClientId("hardcoded-client", realm);
+            UserConsentModel maryHardcodedConsent = currentSession.users().getConsentByClient(realm, mary.getId(), hardcodedClient.getId());
+            Assert.assertEquals(maryHardcodedConsent.getGrantedClientScopes().size(), 0);
+            Assert.assertNotNull("Created Date should be set", maryHardcodedConsent.getCreatedDate());
+            Assert.assertNotNull("Last Updated Date should be set", maryHardcodedConsent.getLastUpdatedDate());
+
+            Assert.assertNull(currentSession.users().getConsentByClient(realm, mary.getId(), barClient.getId()));
+            Assert.assertNull(currentSession.users().getConsentByClient(realm, john.getId(), hardcodedClient.getId()));
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void getAllConsentTest(KeycloakSession session) {
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionACT) -> {
+            KeycloakSession currentSession = sessionACT;
+            RealmModel realm = currentSession.realms().getRealm("original");
+
+            ClientModel fooClient = realm.getClientByClientId("foo-client");
+
+            UserModel john = currentSession.users().getUserByUsername("john", realm);
+            UserModel mary = currentSession.users().getUserByUsername("mary", realm);
+
+            List<UserConsentModel> johnConsents = currentSession.users().getConsents(realm, john.getId());
+            Assert.assertEquals(2, johnConsents.size());
+
+            ClientModel hardcodedClient = currentSession.realms().getClientByClientId("hardcoded-client", realm);
+
+            List<UserConsentModel> maryConsents = currentSession.users().getConsents(realm, mary.getId());
+            Assert.assertEquals(2, maryConsents.size());
+            UserConsentModel maryConsent = maryConsents.get(0);
+            UserConsentModel maryHardcodedConsent = maryConsents.get(1);
+            if (maryConsents.get(0).getClient().getId().equals(hardcodedClient.getId())) {
+                maryConsent = maryConsents.get(1);
+                maryHardcodedConsent = maryConsents.get(0);
+
+            }
+            Assert.assertEquals(maryConsent.getClient().getId(), fooClient.getId());
+            Assert.assertEquals(maryConsent.getGrantedClientScopes().size(), 1);
+            Assert.assertTrue(isClientScopeGranted(realm, "foo", maryConsent));
+
+            Assert.assertEquals(maryHardcodedConsent.getClient().getId(), hardcodedClient.getId());
+            Assert.assertEquals(maryHardcodedConsent.getGrantedClientScopes().size(), 0);
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void updateWithClientScopeRemovalTest(KeycloakSession session) {
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession removalTestSession1) -> {
+            KeycloakSession currentSession = removalTestSession1;
+            RealmModel realm = currentSession.realms().getRealm("original");
+
+            ClientModel fooClient = realm.getClientByClientId("foo-client");
+            UserModel john = currentSession.users().getUserByUsername("john", realm);
+
+            UserConsentModel johnConsent = currentSession.users().getConsentByClient(realm, john.getId(), fooClient.getId());
+            Assert.assertEquals(1, johnConsent.getGrantedClientScopes().size());
+
+            // Remove foo protocol mapper from johnConsent
+            ClientScopeModel fooScope = KeycloakModelUtils.getClientScopeByName(realm, "foo");
+            johnConsent.getGrantedClientScopes().remove(fooScope);
+
+            currentSession.users().updateConsent(realm, john.getId(), johnConsent);
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession removalTestSession2) -> {
+            KeycloakSession currentSession = removalTestSession2;
+            RealmModel realm = currentSession.realms().getRealm("original");
+
+            ClientModel fooClient = realm.getClientByClientId("foo-client");
+            UserModel john = currentSession.users().getUserByUsername("john", realm);
+            UserConsentModel johnConsent = currentSession.users().getConsentByClient(realm, john.getId(), fooClient.getId());
+
+            Assert.assertEquals(johnConsent.getGrantedClientScopes().size(), 0);
+            Assert.assertTrue("Created date should be less than last updated date", johnConsent.getCreatedDate() < johnConsent.getLastUpdatedDate());
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void revokeTest(KeycloakSession session) {
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionRT1) -> {
+            KeycloakSession currentSession = sessionRT1;
+            RealmModel realm = currentSession.realms().getRealm("original");
+
+            ClientModel fooClient = realm.getClientByClientId("foo-client");
+            UserModel john = currentSession.users().getUserByUsername("john", realm);
+            UserModel mary = currentSession.users().getUserByUsername("mary", realm);
+
+            currentSession.users().revokeConsentForClient(realm, john.getId(), fooClient.getId());
+            ClientModel hardcodedClient = currentSession.realms().getClientByClientId("hardcoded-client", realm);
+            currentSession.users().revokeConsentForClient(realm, mary.getId(), hardcodedClient.getId());
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionRT2) -> {
+            KeycloakSession currentSession = sessionRT2;
+            RealmModel realm = currentSession.realms().getRealm("original");
+
+            ClientModel fooClient = realm.getClientByClientId("foo-client");
+            ClientModel hardcodedClient = currentSession.realms().getClientByClientId("hardcoded-client", realm);
+
+            UserModel john = currentSession.users().getUserByUsername("john", realm);
+            Assert.assertNull(currentSession.users().getConsentByClient(realm, john.getId(), fooClient.getId()));
+            UserModel mary = currentSession.users().getUserByUsername("mary", realm);
+            Assert.assertNull(currentSession.users().getConsentByClient(realm, mary.getId(), hardcodedClient.getId()));
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void deleteUserTest(KeycloakSession session) {
+        // Validate user deleted without any referential constraint errors
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionUT) -> {
+            KeycloakSession currentSession = sessionUT;
+            RealmModel realm = currentSession.realms().getRealm("original");
+
+            UserModel john = currentSession.users().getUserByUsername("john", realm);
+            currentSession.users().removeUser(realm, john);
+            UserModel mary = currentSession.users().getUserByUsername("mary", realm);
+            currentSession.users().removeUser(realm, mary);
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void deleteClientScopeTest(KeycloakSession session) {
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionST1) -> {
+            KeycloakSession currentSession = sessionST1;
+            RealmModel realm = currentSession.realms().getRealm("original");
+
+            ClientScopeModel fooScope = KeycloakModelUtils.getClientScopeByName(realm, "foo");
+            realm.removeClientScope(fooScope.getId());
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionST2) -> {
+            KeycloakSession currentSession = sessionST2;
+            RealmModel realm = currentSession.realms().getRealm("original");
+
+            ClientModel fooClient = realm.getClientByClientId("foo-client");
+
+            UserModel john = currentSession.users().getUserByUsername("john", realm);
+            UserConsentModel johnConsent = currentSession.users().getConsentByClient(realm, john.getId(), fooClient.getId());
+
+            Assert.assertEquals(johnConsent.getGrantedClientScopes().size(), 0);
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void deleteClientTest(KeycloakSession session) {
+
+        AtomicReference<String> barClientID = new AtomicReference<>();
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionDCT1) -> {
+            KeycloakSession currentSession = sessionDCT1;
+            RealmModel realm = currentSession.realms().getRealm("original");
+
+            ClientModel barClient = realm.getClientByClientId("bar-client");
+            barClientID.set(barClient.getId());
+
+            realm.removeClient(barClient.getId());
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionDCT2) -> {
+            KeycloakSession currentSession = sessionDCT2;
+            RealmModel realm = currentSession.realms().getRealm("original");
+
+            ClientModel fooClient = realm.getClientByClientId("foo-client");
+            Assert.assertNull(realm.getClientByClientId("bar-client"));
+
+            UserModel john = currentSession.users().getUserByUsername("john", realm);
+            ClientModel barClient = realm.getClientByClientId("bar-client");
+
+            UserConsentModel johnFooConsent = currentSession.users().getConsentByClient(realm, john.getId(), fooClient.getId());
+            Assert.assertEquals(johnFooConsent.getGrantedClientScopes().size(), 1);
+            Assert.assertTrue(isClientScopeGranted(realm, "foo", johnFooConsent));
+
+            Assert.assertNull(currentSession.users().getConsentByClient(realm, john.getId(), barClientID.get()));
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void deleteClientStorageTest(KeycloakSession session) {
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionCST1) -> {
+            KeycloakSession currentSession = sessionCST1;
+            RealmModel realm = currentSession.realms().getRealm("original");
+
+            realm.removeComponent(clientStorageComponent);
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionCST2) -> {
+            KeycloakSession currentSession = sessionCST2;
+            RealmModel realm = currentSession.realms().getRealm("original");
+
+            ClientModel hardcodedClient = currentSession.realms().getClientByClientId("hardcoded-client", realm);
+            Assert.assertNull(hardcodedClient);
+
+            UserModel mary = currentSession.users().getUserByUsername("mary", realm);
+
+            List<UserConsentModel> maryConsents = currentSession.users().getConsents(realm, mary.getId());
+            Assert.assertEquals(1, maryConsents.size());
+        });
+    }
+
+    private boolean isClientScopeGranted(RealmModel realm, String scopeName, UserConsentModel consentModel) {
+        ClientScopeModel clientScope = KeycloakModelUtils.getClientScopeByName(realm, scopeName);
+        return consentModel.isClientScopeGranted(clientScope);
+    }
+
+    @Override
+    public void configureTestRealm(RealmRepresentation testRealm) {
+
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserConsentWithUserStorageModelTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserConsentWithUserStorageModelTest.java
new file mode 100644
index 0000000..23a83d0
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserConsentWithUserStorageModelTest.java
@@ -0,0 +1,420 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.model;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.TargetsContainer;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.models.*;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.storage.UserStorageProviderModel;
+import org.keycloak.storage.client.ClientStorageProviderModel;
+import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
+import org.keycloak.testsuite.arquillian.annotation.ModelTest;
+import org.keycloak.testsuite.federation.HardcodedClientStorageProviderFactory;
+import org.keycloak.testsuite.federation.UserMapStorageFactory;
+import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.keycloak.testsuite.arquillian.DeploymentTargetModifier.AUTH_SERVER_CURRENT;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UserConsentWithUserStorageModelTest extends AbstractTestRealmKeycloakTest {
+
+    @Deployment
+    @TargetsContainer(AUTH_SERVER_CURRENT)
+    public static WebArchive deploy() {
+        return RunOnServerDeployment.create(UserResource.class, UserConsentWithUserStorageModelTest.class)
+                .addPackages(true,
+                        "org.keycloak.testsuite",
+                        "org.keycloak.testsuite.model");
+    }
+
+    private static ComponentModel clientStorageComponent;
+
+    @Before
+    public void before() {
+        testingClient.server().run(currentSession -> {
+            setupEnv(currentSession);
+        });
+    }
+
+    @After
+    public void after() {
+        testingClient.server().run(session -> {
+
+            RealmManager realmManager = new RealmManager(session);
+            RealmModel realm = realmManager.getRealmByName("original");
+
+            if (realm != null) {
+
+                session.sessions().removeUserSessions(realm);
+                UserModel user = session.users().getUserByUsername("user", realm);
+                UserModel user1 = session.users().getUserByUsername("user1", realm);
+                UserModel user2 = session.users().getUserByUsername("user2", realm);
+                UserModel user3 = session.users().getUserByUsername("user3", realm);
+
+                UserManager um = new UserManager(session);
+                if (user != null) {
+                    um.removeUser(realm, user);
+                }
+                if (user1 != null) {
+                    um.removeUser(realm, user1);
+                }
+                if (user2 != null) {
+                    um.removeUser(realm, user2);
+                }
+                if (user3 != null) {
+                    um.removeUser(realm, user3);
+                }
+                realmManager.removeRealm(realm);
+            }
+        });
+    }
+
+    public static void setupEnv(KeycloakSession session) {
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionSetUpEnv) -> {
+            KeycloakSession currentSession = sessionSetUpEnv;
+
+            RealmManager realmManager = new RealmManager(currentSession);
+            RealmModel realm = realmManager.createRealm("original");
+
+            UserStorageProviderModel model = new UserStorageProviderModel();
+            model.setName("memory");
+            model.setPriority(0);
+            model.setProviderId(UserMapStorageFactory.PROVIDER_ID);
+            model.setParentId(realm.getId());
+            realm.addComponentModel(model);
+
+            ClientModel fooClient = realm.addClient("foo-client");
+            ClientModel barClient = realm.addClient("bar-client");
+
+            ClientScopeModel fooScope = realm.addClientScope("foo");
+            fooScope.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
+
+            ClientScopeModel barScope = realm.addClientScope("bar");
+            fooScope.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
+
+            UserModel john = currentSession.users().addUser(realm, "john");
+            UserModel mary = currentSession.users().addUser(realm, "mary");
+
+            UserConsentModel johnFooGrant = new UserConsentModel(fooClient);
+            johnFooGrant.addGrantedClientScope(fooScope);
+            realmManager.getSession().users().addConsent(realm, john.getId(), johnFooGrant);
+
+            UserConsentModel johnBarGrant = new UserConsentModel(barClient);
+            johnBarGrant.addGrantedClientScope(barScope);
+
+            // Update should fail as grant doesn't yet exists
+            try {
+                currentSession.users().updateConsent(realm, john.getId(), johnBarGrant);
+                Assert.fail("Not expected to end here");
+            } catch (ModelException expected) {
+            }
+
+            realmManager.getSession().users().addConsent(realm, john.getId(), johnBarGrant);
+
+            UserConsentModel maryFooGrant = new UserConsentModel(fooClient);
+            maryFooGrant.addGrantedClientScope(fooScope);
+            realmManager.getSession().users().addConsent(realm, mary.getId(), maryFooGrant);
+
+            ClientStorageProviderModel clientStorage = new ClientStorageProviderModel();
+            clientStorage.setProviderId(HardcodedClientStorageProviderFactory.PROVIDER_ID);
+            clientStorage.getConfig().putSingle(HardcodedClientStorageProviderFactory.CLIENT_ID, "hardcoded-client");
+            clientStorage.getConfig().putSingle(HardcodedClientStorageProviderFactory.REDIRECT_URI, "http://localhost:8081/*");
+            clientStorage.getConfig().putSingle(HardcodedClientStorageProviderFactory.CONSENT, "true");
+            clientStorage.setParentId(realm.getId());
+            clientStorageComponent = realm.addComponentModel(clientStorage);
+
+            ClientModel hardcodedClient = currentSession.realms().getClientByClientId("hardcoded-client", realm);
+
+            Assert.assertNotNull(hardcodedClient);
+
+            UserConsentModel maryHardcodedGrant = new UserConsentModel(hardcodedClient);
+            realmManager.getSession().users().addConsent(realm, mary.getId(), maryHardcodedGrant);
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void basicConsentTest(KeycloakSession session) {
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession currentSessionCT) -> {
+            KeycloakSession currentSession = currentSessionCT;
+            RealmModel realm = currentSession.realms().getRealmByName("original");
+
+            ClientModel fooClient = realm.getClientByClientId("foo-client");
+            ClientModel barClient = realm.getClientByClientId("bar-client");
+
+            UserModel john = currentSessionCT.users().getUserByUsername("john", realm);
+            UserModel mary = currentSessionCT.users().getUserByUsername("mary", realm);
+
+            UserConsentModel johnFooConsent = currentSession.users().getConsentByClient(realm, john.getId(), fooClient.getId());
+            Assert.assertEquals(johnFooConsent.getGrantedClientScopes().size(), 1);
+            Assert.assertTrue(isClientScopeGranted(realm, "foo", johnFooConsent));
+            Assert.assertNotNull("Created Date should be set", johnFooConsent.getCreatedDate());
+            Assert.assertNotNull("Last Updated Date should be set", johnFooConsent.getLastUpdatedDate());
+
+            UserConsentModel johnBarConsent = currentSession.users().getConsentByClient(realm, john.getId(), barClient.getId());
+            Assert.assertEquals(johnBarConsent.getGrantedClientScopes().size(), 1);
+            Assert.assertTrue(isClientScopeGranted(realm, "bar", johnBarConsent));
+            Assert.assertNotNull("Created Date should be set", johnBarConsent.getCreatedDate());
+            Assert.assertNotNull("Last Updated Date should be set", johnBarConsent.getLastUpdatedDate());
+
+            UserConsentModel maryConsent = currentSession.users().getConsentByClient(realm, mary.getId(), fooClient.getId());
+            Assert.assertEquals(maryConsent.getGrantedClientScopes().size(), 1);
+            Assert.assertTrue(isClientScopeGranted(realm, "foo", maryConsent));
+            Assert.assertNotNull("Created Date should be set", maryConsent.getCreatedDate());
+            Assert.assertNotNull("Last Updated Date should be set", maryConsent.getLastUpdatedDate());
+
+            ClientModel hardcodedClient = currentSessionCT.realms().getClientByClientId("hardcoded-client", realm);
+            UserConsentModel maryHardcodedConsent = currentSession.users().getConsentByClient(realm, mary.getId(), hardcodedClient.getId());
+            Assert.assertEquals(maryHardcodedConsent.getGrantedClientScopes().size(), 0);
+            Assert.assertNotNull("Created Date should be set", maryHardcodedConsent.getCreatedDate());
+            Assert.assertNotNull("Last Updated Date should be set", maryHardcodedConsent.getLastUpdatedDate());
+
+            Assert.assertNull(currentSession.users().getConsentByClient(realm, mary.getId(), barClient.getId()));
+            Assert.assertNull(currentSession.users().getConsentByClient(realm, john.getId(), hardcodedClient.getId()));
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void getAllConsentTest(KeycloakSession session) {
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession currentSessionACT) -> {
+            KeycloakSession currentSession = currentSessionACT;
+            RealmModel realm = currentSession.realms().getRealmByName("original");
+
+            ClientModel fooClient = realm.getClientByClientId("foo-client");
+
+            UserModel john = currentSessionACT.users().getUserByUsername("john", realm);
+            UserModel mary = currentSessionACT.users().getUserByUsername("mary", realm);
+
+            List<UserConsentModel> johnConsents = currentSession.users().getConsents(realm, john.getId());
+            Assert.assertEquals(2, johnConsents.size());
+
+            ClientModel hardcodedClient = currentSessionACT.realms().getClientByClientId("hardcoded-client", realm);
+
+            List<UserConsentModel> maryConsents = currentSession.users().getConsents(realm, mary.getId());
+            Assert.assertEquals(2, maryConsents.size());
+            UserConsentModel maryConsent = maryConsents.get(0);
+            UserConsentModel maryHardcodedConsent = maryConsents.get(1);
+            if (maryConsents.get(0).getClient().getId().equals(hardcodedClient.getId())) {
+                maryConsent = maryConsents.get(1);
+                maryHardcodedConsent = maryConsents.get(0);
+
+            }
+            Assert.assertEquals(maryConsent.getClient().getId(), fooClient.getId());
+            Assert.assertEquals(maryConsent.getGrantedClientScopes().size(), 1);
+            Assert.assertTrue(isClientScopeGranted(realm, "foo", maryConsent));
+
+            Assert.assertEquals(maryHardcodedConsent.getClient().getId(), hardcodedClient.getId());
+            Assert.assertEquals(maryHardcodedConsent.getGrantedClientScopes().size(), 0);
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void updateWithClientScopeRemovalTest(KeycloakSession session) {
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionScopeRemoval1) -> {
+            KeycloakSession currentSession = sessionScopeRemoval1;
+            RealmModel realm = currentSession.realms().getRealmByName("original");
+
+            ClientModel fooClient = realm.getClientByClientId("foo-client");
+            UserModel john = currentSession.users().getUserByUsername("john", realm);
+
+            UserConsentModel johnConsent = currentSession.users().getConsentByClient(realm, john.getId(), fooClient.getId());
+            Assert.assertEquals(1, johnConsent.getGrantedClientScopes().size());
+
+            // Remove foo protocol mapper from johnConsent
+            ClientScopeModel fooScope = KeycloakModelUtils.getClientScopeByName(realm, "foo");
+            johnConsent.getGrantedClientScopes().remove(fooScope);
+
+            currentSession.users().updateConsent(realm, john.getId(), johnConsent);
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionScopeRemoval2) -> {
+            KeycloakSession currentSession = sessionScopeRemoval2;
+            RealmModel realm = currentSession.realms().getRealmByName("original");
+
+            ClientModel fooClient = realm.getClientByClientId("foo-client");
+            UserModel john = currentSession.users().getUserByUsername("john", realm);
+            UserConsentModel johnConsent = currentSession.users().getConsentByClient(realm, john.getId(), fooClient.getId());
+
+            Assert.assertEquals(johnConsent.getGrantedClientScopes().size(), 0);
+            Assert.assertTrue("Created date should be less than last updated date", johnConsent.getCreatedDate() < johnConsent.getLastUpdatedDate());
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void revokeTest(KeycloakSession session) {
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionRevoke1) -> {
+            KeycloakSession currentSession = sessionRevoke1;
+            RealmModel realm = currentSession.realms().getRealmByName("original");
+
+            ClientModel fooClient = realm.getClientByClientId("foo-client");
+            UserModel john = currentSession.users().getUserByUsername("john", realm);
+            UserModel mary = currentSession.users().getUserByUsername("mary", realm);
+
+            currentSession.users().revokeConsentForClient(realm, john.getId(), fooClient.getId());
+            ClientModel hardcodedClient = currentSession.realms().getClientByClientId("hardcoded-client", realm);
+            currentSession.users().revokeConsentForClient(realm, mary.getId(), hardcodedClient.getId());
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionRevoke2) -> {
+            KeycloakSession currentSession = sessionRevoke2;
+            RealmModel realm = currentSession.realms().getRealmByName("original");
+
+            ClientModel fooClient = realm.getClientByClientId("foo-client");
+            ClientModel hardcodedClient = currentSession.realms().getClientByClientId("hardcoded-client", realm);
+
+            UserModel john = currentSession.users().getUserByUsername("john", realm);
+            Assert.assertNull(currentSession.users().getConsentByClient(realm, john.getId(), fooClient.getId()));
+
+            UserModel mary = currentSession.users().getUserByUsername("mary", realm);
+            Assert.assertNull(currentSession.users().getConsentByClient(realm, mary.getId(), hardcodedClient.getId()));
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void deleteUserTest(KeycloakSession session) {
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionDelete) -> {
+            // Validate user deleted without any referential constraint errors
+            KeycloakSession currentSession = sessionDelete;
+            RealmModel realm = currentSession.realms().getRealmByName("original");
+
+            UserModel john = currentSession.users().getUserByUsername("john", realm);
+            currentSession.users().removeUser(realm, john);
+            UserModel mary = currentSession.users().getUserByUsername("mary", realm);
+            currentSession.users().removeUser(realm, mary);
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void deleteClientScopeTest(KeycloakSession session) {
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesDelClScope1) -> {
+            KeycloakSession currentSession = sesDelClScope1;
+            RealmModel realm = currentSession.realms().getRealmByName("original");
+
+            ClientModel fooClient = realm.getClientByClientId("foo-client");
+            ClientScopeModel fooScope = KeycloakModelUtils.getClientScopeByName(realm, "foo");
+            realm.removeClientScope(fooScope.getId());
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesDelClScope2) -> {
+            KeycloakSession currentSession = sesDelClScope2;
+            RealmModel realm = currentSession.realms().getRealmByName("original");
+
+            ClientModel fooClient = realm.getClientByClientId("foo-client");
+            UserModel john = currentSession.users().getUserByUsername("john", realm);
+            UserConsentModel johnConsent = currentSession.users().getConsentByClient(realm, john.getId(), fooClient.getId());
+
+            Assert.assertEquals(johnConsent.getGrantedClientScopes().size(), 0);
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void deleteClientTest(KeycloakSession session) {
+        AtomicReference<String> barClientID = new AtomicReference<>();
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesDelClient1) -> {
+            KeycloakSession currentSession = sesDelClient1;
+            RealmManager realmManager = new RealmManager(currentSession);
+            RealmModel realm = realmManager.getRealmByName("original");
+
+            ClientModel barClient = realm.getClientByClientId("bar-client");
+            barClientID.set(barClient.getId());
+
+            realm.removeClient(barClient.getId());
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesDelClient2) -> {
+            KeycloakSession currentSession = sesDelClient2;
+            RealmManager realmManager = new RealmManager(currentSession);
+            RealmModel realm = realmManager.getRealm("original");
+
+            ClientModel fooClient = realm.getClientByClientId("foo-client");
+            Assert.assertNull(realm.getClientByClientId("bar-client"));
+
+            UserModel john = realmManager.getSession().users().getUserByUsername("john", realm);
+
+            UserConsentModel johnFooConsent = realmManager.getSession().users().getConsentByClient(realm, john.getId(), fooClient.getId());
+            Assert.assertEquals(johnFooConsent.getGrantedClientScopes().size(), 1);
+            Assert.assertTrue(isClientScopeGranted(realm, "foo", johnFooConsent));
+
+            Assert.assertNull(realmManager.getSession().users().getConsentByClient(realm, john.getId(), barClientID.get()));
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void deleteClientStorageTest(KeycloakSession session) {
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesDelClientStore1) -> {
+            KeycloakSession currentSession = sesDelClientStore1;
+            RealmModel realm = currentSession.realms().getRealmByName("original");
+
+            realm.removeComponent(clientStorageComponent);
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesDelClientStore2) -> {
+            KeycloakSession currentSession = sesDelClientStore2;
+            RealmModel realm = currentSession.realms().getRealmByName("original");
+
+            ClientModel hardcodedClient = currentSession.realms().getClientByClientId("hardcoded-client", realm);
+            Assert.assertNull(hardcodedClient);
+
+            UserModel mary = currentSession.users().getUserByUsername("mary", realm);
+
+            List<UserConsentModel> maryConsents = currentSession.users().getConsents(realm, mary.getId());
+            Assert.assertEquals(1, maryConsents.size());
+        });
+    }
+
+    private boolean isClientScopeGranted(RealmModel realm, String scopeName, UserConsentModel consentModel) {
+        ClientScopeModel clientScope = KeycloakModelUtils.getClientScopeByName(realm, scopeName);
+        return consentModel.isClientScopeGranted(clientScope);
+    }
+
+    @Override
+    public void configureTestRealm(RealmRepresentation testRealm) {
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserModelTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserModelTest.java
new file mode 100755
index 0000000..d8db8e9
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserModelTest.java
@@ -0,0 +1,615 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.model;
+
+import com.google.common.collect.ImmutableMap;
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.TargetsContainer;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.models.*;
+import org.keycloak.models.UserModel.RequiredAction;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.services.managers.ClientManager;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
+import org.keycloak.testsuite.arquillian.annotation.ModelTest;
+import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
+
+import java.util.*;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.junit.Assert.assertNotNull;
+import static org.keycloak.testsuite.arquillian.DeploymentTargetModifier.AUTH_SERVER_CURRENT;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class UserModelTest extends AbstractTestRealmKeycloakTest {
+
+    @Deployment
+    @TargetsContainer(AUTH_SERVER_CURRENT)
+    public static WebArchive deploy() {
+        return RunOnServerDeployment.create(UserResource.class, UserModelTest.class)
+                .addPackages(true,
+                        "org.keycloak.testsuite",
+                        "org.keycloak.testsuite.model",
+                        "com.google.common");
+    }
+
+    @After
+    public void after() {
+        testingClient.server().run(session -> {
+
+            RealmModel realm = session.realms().getRealmByName("original");
+
+            if (realm != null) {
+                session.sessions().removeUserSessions(realm);
+                UserModel user = session.users().getUserByUsername("user", realm);
+                UserModel user1 = session.users().getUserByUsername("user1", realm);
+                UserModel user2 = session.users().getUserByUsername("user2", realm);
+                UserModel user3 = session.users().getUserByUsername("user3", realm);
+
+                UserManager um = new UserManager(session);
+                if (user != null) {
+                    um.removeUser(realm, user);
+                }
+                if (user1 != null) {
+                    um.removeUser(realm, user1);
+                }
+                if (user2 != null) {
+                    um.removeUser(realm, user2);
+                }
+                if (user3 != null) {
+                    um.removeUser(realm, user3);
+                }
+                session.realms().removeRealm(realm.getId());
+            }
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void persistUser(KeycloakSession session) {
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesPersistUser) -> {
+            KeycloakSession currentSession = sesPersistUser;
+            RealmModel realm = currentSession.realms().createRealm("original");
+
+            UserModel user = currentSession.users().addUser(realm, "user");
+            user.setFirstName("first-name");
+            user.setLastName("last-name");
+            user.setEmail("email");
+            assertNotNull(user.getCreatedTimestamp());
+            // test that timestamp is current with 10s tollerance
+            Assert.assertTrue((System.currentTimeMillis() - user.getCreatedTimestamp()) < 10000);
+
+            user.addRequiredAction(RequiredAction.CONFIGURE_TOTP);
+            user.addRequiredAction(RequiredAction.UPDATE_PASSWORD);
+
+            RealmModel searchRealm = currentSession.realms().getRealm(realm.getId());
+            UserModel persisted = currentSession.users().getUserByUsername("user", searchRealm);
+
+            assertEquals(user, persisted);
+
+            searchRealm = currentSession.realms().getRealm(realm.getId());
+            UserModel persisted2 = currentSession.users().getUserById(user.getId(), searchRealm);
+            assertEquals(user, persisted2);
+
+            Map<String, String> attributes = new HashMap<>();
+            attributes.put(UserModel.LAST_NAME, "last-name");
+            List<UserModel> search = currentSession.users().searchForUser(attributes, realm);
+            Assert.assertEquals(search.size(), 1);
+            Assert.assertEquals(search.get(0).getUsername(), "user");
+
+            attributes.clear();
+            attributes.put(UserModel.EMAIL, "email");
+            search = currentSession.users().searchForUser(attributes, realm);
+            Assert.assertEquals(search.size(), 1);
+            Assert.assertEquals(search.get(0).getUsername(), "user");
+
+            attributes.clear();
+            attributes.put(UserModel.LAST_NAME, "last-name");
+            attributes.put(UserModel.EMAIL, "email");
+            search = currentSession.users().searchForUser(attributes, realm);
+            Assert.assertEquals(search.size(), 1);
+            Assert.assertEquals(search.get(0).getUsername(), "user");
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void webOriginSetTest(KeycloakSession session) {
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesWebOrigin) -> {
+            KeycloakSession currentSession = sesWebOrigin;
+            RealmModel realm = currentSession.realms().createRealm("original");
+
+            ClientModel client = realm.addClient("user");
+
+            Assert.assertTrue(client.getWebOrigins().isEmpty());
+
+            client.addWebOrigin("origin-1");
+            Assert.assertEquals(1, client.getWebOrigins().size());
+
+            client.addWebOrigin("origin-2");
+            Assert.assertEquals(2, client.getWebOrigins().size());
+
+            client.removeWebOrigin("origin-2");
+            Assert.assertEquals(1, client.getWebOrigins().size());
+
+            client.removeWebOrigin("origin-1");
+            Assert.assertTrue(client.getWebOrigins().isEmpty());
+
+            client = realm.addClient("oauthclient2");
+
+            Assert.assertTrue(client.getWebOrigins().isEmpty());
+
+            client.addWebOrigin("origin-1");
+            Assert.assertEquals(1, client.getWebOrigins().size());
+
+            client.addWebOrigin("origin-2");
+            Assert.assertEquals(2, client.getWebOrigins().size());
+
+            client.removeWebOrigin("origin-2");
+            Assert.assertEquals(1, client.getWebOrigins().size());
+
+            client.removeWebOrigin("origin-1");
+            Assert.assertTrue(client.getWebOrigins().isEmpty());
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void testUserRequiredActions(KeycloakSession session) throws Exception {
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesUserReqActions) -> {
+            KeycloakSession currentSession = sesUserReqActions;
+            RealmModel realm = currentSession.realms().createRealm("original");
+
+            UserModel user = currentSession.users().addUser(realm, "user");
+
+            Assert.assertTrue(user.getRequiredActions().isEmpty());
+
+            user.addRequiredAction(RequiredAction.CONFIGURE_TOTP);
+            String id = realm.getId();
+
+            realm = currentSession.realms().getRealm(id);
+            user = currentSession.users().getUserByUsername("user", realm);
+
+            Assert.assertEquals(1, user.getRequiredActions().size());
+            Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP.name()));
+
+            user.addRequiredAction(RequiredAction.CONFIGURE_TOTP);
+            user = currentSession.users().getUserByUsername("user", realm);
+
+            Assert.assertEquals(1, user.getRequiredActions().size());
+            Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP.name()));
+
+            user.addRequiredAction(RequiredAction.VERIFY_EMAIL.name());
+            user = currentSession.users().getUserByUsername("user", realm);
+
+            Assert.assertEquals(2, user.getRequiredActions().size());
+            Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP.name()));
+            Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL.name()));
+
+            user.removeRequiredAction(RequiredAction.CONFIGURE_TOTP.name());
+            user = currentSession.users().getUserByUsername("user", realm);
+
+            Assert.assertEquals(1, user.getRequiredActions().size());
+            Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL.name()));
+
+            user.removeRequiredAction(RequiredAction.VERIFY_EMAIL.name());
+            user = currentSession.users().getUserByUsername("user", realm);
+
+            Assert.assertTrue(user.getRequiredActions().isEmpty());
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void testUserMultipleAttributes(KeycloakSession session) throws Exception {
+        AtomicReference<List<String>> attrValsAtomic = new AtomicReference<>();
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesMultipleAtr1) -> {
+            KeycloakSession currentSession = sesMultipleAtr1;
+            RealmModel realm = currentSession.realms().createRealm("original");
+
+            UserModel user = currentSession.users().addUser(realm, "user");
+            currentSession.users().addUser(realm, "user-noattrs");
+
+            user.setSingleAttribute("key1", "value1");
+
+            List<String> attrVals = new ArrayList<>(Arrays.asList("val21", "val22"));
+            attrValsAtomic.set(attrVals);
+
+            user.setAttribute("key2", attrVals);
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesMultipleAtr2) -> {
+            KeycloakSession currentSession = sesMultipleAtr2;
+            RealmModel realm = currentSession.realms().getRealmByName("original");
+
+            // Test read attributes
+            UserModel user = currentSession.users().getUserByUsername("user", realm);
+
+            List<String> attrVals = attrValsAtomic.get();
+
+            attrVals = user.getAttribute("key1");
+            Assert.assertEquals(1, attrVals.size());
+            Assert.assertEquals("value1", attrVals.get(0));
+            Assert.assertEquals("value1", user.getFirstAttribute("key1"));
+
+            attrVals = user.getAttribute("key2");
+            Assert.assertEquals(2, attrVals.size());
+            Assert.assertTrue(attrVals.contains("val21"));
+            Assert.assertTrue(attrVals.contains("val22"));
+
+            attrVals = user.getAttribute("key3");
+            Assert.assertTrue(attrVals.isEmpty());
+            Assert.assertNull(user.getFirstAttribute("key3"));
+
+            Map<String, List<String>> allAttrVals = user.getAttributes();
+            Assert.assertEquals(2, allAttrVals.size());
+            Assert.assertEquals(allAttrVals.get("key1"), user.getAttribute("key1"));
+            Assert.assertEquals(allAttrVals.get("key2"), user.getAttribute("key2"));
+
+            // Test remove and rewrite attribute
+            user.removeAttribute("key1");
+            user.setSingleAttribute("key2", "val23");
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesMultipleAtr3) -> {
+            KeycloakSession currentSession = sesMultipleAtr3;
+            RealmModel realm = currentSession.realms().getRealmByName("original");
+
+            UserModel user = currentSession.users().getUserByUsername("user", realm);
+            Assert.assertNull(user.getFirstAttribute("key1"));
+
+            List<String> attrVals = attrValsAtomic.get();
+            attrVals = user.getAttribute("key2");
+
+            Assert.assertEquals(1, attrVals.size());
+            Assert.assertEquals("val23", attrVals.get(0));
+
+        });
+    }
+
+    // KEYCLOAK-3494
+    @Test
+    @ModelTest
+    public void testUpdateUserAttribute(KeycloakSession session) throws Exception {
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesUpdateAtr1) -> {
+            KeycloakSession currentSession = sesUpdateAtr1;
+            RealmModel realm = currentSession.realms().createRealm("original");
+
+            UserModel user = currentSession.users().addUser(realm, "user");
+
+            user.setSingleAttribute("key1", "value1");
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesUpdateAtr2) -> {
+            KeycloakSession currentSession = sesUpdateAtr2;
+            RealmModel realm = currentSession.realms().getRealmByName("original");
+
+            UserModel user = currentSession.users().getUserByUsername("user", realm);
+
+            // Update attribute
+            List<String> attrVals = new ArrayList<>(Arrays.asList("val2"));
+            user.setAttribute("key1", attrVals);
+            Map<String, List<String>> allAttrVals = user.getAttributes();
+
+            // Ensure same transaction is able to see updated value
+            Assert.assertEquals(1, allAttrVals.size());
+            Assert.assertEquals(allAttrVals.get("key1"), Arrays.asList("val2"));
+
+        });
+    }
+
+    // KEYCLOAK-3608
+    @Test
+    @ModelTest
+    public void testUpdateUserSingleAttribute(KeycloakSession session) {
+
+        AtomicReference<Map<String, List<String>>> expectedAtomic = new AtomicReference<>();
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesUpdateUserSingleAtr) -> {
+            KeycloakSession currentSession = sesUpdateUserSingleAtr;
+            RealmModel realm = currentSession.realms().createRealm("original");
+
+
+            Map<String, List<String>> expected = ImmutableMap.of(
+                    "key1", Arrays.asList("value3"),
+                    "key2", Arrays.asList("value2"));
+            expectedAtomic.set(expected);
+
+            UserModel user = currentSession.users().addUser(realm, "user");
+
+            user.setSingleAttribute("key1", "value1");
+            user.setSingleAttribute("key2", "value2");
+
+            // Overwrite the first attribute
+            user.setSingleAttribute("key1", "value3");
+
+            Assert.assertEquals(expected, user.getAttributes());
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesUpdateUserSingleAtr2) -> {
+            KeycloakSession currentSession = sesUpdateUserSingleAtr2;
+            RealmModel realm = currentSession.realms().getRealmByName("original");
+
+            Map<String, List<String>> expected = expectedAtomic.get();
+            Assert.assertEquals(expected, currentSession.users().getUserByUsername("user", realm).getAttributes());
+
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void testSearchByString(KeycloakSession session) {
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesSearchString1) -> {
+            KeycloakSession currentSession = sesSearchString1;
+            RealmModel realm = currentSession.realms().createRealm("original");
+
+            currentSession.users().addUser(realm, "user1");
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesSearchString1) -> {
+            KeycloakSession currentSession = sesSearchString1;
+            RealmModel realm = currentSession.realms().getRealmByName("original");
+
+            UserModel user1 = currentSession.users().getUserByUsername("user1", realm);
+
+            List<UserModel> users = currentSession.users().searchForUser("user", realm, 0, 7);
+            Assert.assertTrue(users.contains(user1));
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void testSearchByUserAttribute(KeycloakSession session) throws Exception {
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesSearchAtr1) -> {
+            KeycloakSession currentSession = sesSearchAtr1;
+            RealmModel realm = currentSession.realms().createRealm("original");
+
+            UserModel user1 = currentSession.users().addUser(realm, "user1");
+            UserModel user2 = currentSession.users().addUser(realm, "user2");
+            UserModel user3 = currentSession.users().addUser(realm, "user3");
+            RealmModel otherRealm = currentSession.realms().createRealm("other");
+            UserModel otherRealmUser = currentSession.users().addUser(otherRealm, "user1");
+
+            user1.setSingleAttribute("key1", "value1");
+            user1.setSingleAttribute("key2", "value21");
+
+            user2.setSingleAttribute("key1", "value1");
+            user2.setSingleAttribute("key2", "value22");
+
+            user3.setSingleAttribute("key2", "value21");
+
+            otherRealmUser.setSingleAttribute("key2", "value21");
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesSearchAtr2) -> {
+            KeycloakSession currentSession = sesSearchAtr2;
+            RealmModel realm = currentSession.realms().getRealmByName("original");
+
+            UserModel user1 = currentSession.users().getUserByUsername("user1", realm);
+            UserModel user2 = currentSession.users().getUserByUsername("user2", realm);
+            UserModel user3 = currentSession.users().getUserByUsername("user3", realm);
+
+            List<UserModel> users = currentSession.users().searchForUserByUserAttribute("key1", "value1", realm);
+            Assert.assertEquals(2, users.size());
+            Assert.assertTrue(users.contains(user1));
+            Assert.assertTrue(users.contains(user2));
+
+            users = currentSession.users().searchForUserByUserAttribute("key2", "value21", realm);
+            Assert.assertEquals(2, users.size());
+            Assert.assertTrue(users.contains(user1));
+            Assert.assertTrue(users.contains(user3));
+
+            users = currentSession.users().searchForUserByUserAttribute("key2", "value22", realm);
+            Assert.assertEquals(1, users.size());
+            Assert.assertTrue(users.contains(user2));
+
+            users = currentSession.users().searchForUserByUserAttribute("key3", "value3", realm);
+            Assert.assertEquals(0, users.size());
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void testServiceAccountLink(KeycloakSession session) throws Exception {
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesServiceLink1) -> {
+            KeycloakSession currentSession = sesServiceLink1;
+            RealmModel realm = currentSession.realms().createRealm("original");
+
+            ClientModel client = realm.addClient("foo");
+
+            UserModel user1 = currentSession.users().addUser(realm, "user1");
+            user1.setFirstName("John");
+            user1.setLastName("Doe");
+
+            UserModel user2 = currentSession.users().addUser(realm, "user2");
+            user2.setFirstName("John");
+            user2.setLastName("Doe");
+
+            // Search
+            Assert.assertNull(currentSession.users().getServiceAccount(client));
+            List<UserModel> users = currentSession.users().searchForUser("John Doe", realm);
+            Assert.assertEquals(2, users.size());
+            Assert.assertTrue(users.contains(user1));
+            Assert.assertTrue(users.contains(user2));
+
+            // Link service account
+            user1.setServiceAccountClientLink(client.getId());
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesServiceLink2) -> {
+            KeycloakSession currentSession = sesServiceLink2;
+            RealmModel realm = currentSession.realms().getRealmByName("original");
+
+            UserModel user1 = currentSession.users().getUserByUsername("user1", realm);
+            UserModel user2 = currentSession.users().getUserByUsername("user2", realm);
+
+            // Search and assert service account user not found
+            ClientModel client = realm.getClientByClientId("foo");
+            UserModel searched = currentSession.users().getServiceAccount(client);
+            Assert.assertEquals(searched, user1);
+            List<UserModel> users = currentSession.users().searchForUser("John Doe", realm);
+            Assert.assertEquals(1, users.size());
+            Assert.assertFalse(users.contains(user1));
+            Assert.assertTrue(users.contains(user2));
+
+            users = currentSession.users().getUsers(realm, false);
+            Assert.assertEquals(1, users.size());
+            Assert.assertFalse(users.contains(user1));
+            Assert.assertTrue(users.contains(user2));
+
+            users = currentSession.users().getUsers(realm, true);
+            Assert.assertEquals(2, users.size());
+            Assert.assertTrue(users.contains(user1));
+            Assert.assertTrue(users.contains(user2));
+
+            Assert.assertEquals(2, currentSession.users().getUsersCount(realm, true));
+            Assert.assertEquals(1, currentSession.users().getUsersCount(realm, false));
+
+            // Remove client
+            RealmManager realmMgr = new RealmManager(currentSession);
+            ClientManager clientMgr = new ClientManager(realmMgr);
+
+            clientMgr.removeClient(realm, client);
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesServiceLink3) -> {
+            KeycloakSession currentSession = sesServiceLink3;
+            RealmModel realm = currentSession.realms().getRealmByName("original");
+            // Assert service account removed as well
+            Assert.assertNull(currentSession.users().getUserByUsername("user1", realm));
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void testGrantToAll(KeycloakSession session) {
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesGrantToAll1) -> {
+            KeycloakSession currentSession = sesGrantToAll1;
+
+            if (currentSession.realms().getRealm("realm1") != null)
+                currentSession.realms().removeRealm("realm1");
+
+            RealmModel realm1 = currentSession.realms().createRealm("realm1");
+
+            realm1.addRole("role1");
+            currentSession.users().addUser(realm1, "user1");
+            currentSession.users().addUser(realm1, "user2");
+
+            if (currentSession.realms().getRealm("realm2") != null)
+                currentSession.realms().removeRealm("realm2");
+
+            RealmModel realm2 = currentSession.realms().createRealm("realm2");
+            currentSession.users().addUser(realm2, "user1");
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesGrantToAll2) -> {
+            KeycloakSession currentSession = sesGrantToAll2;
+            RealmModel realm1 = currentSession.realms().getRealmByName("realm1");
+
+            RoleModel role1 = realm1.getRole("role1");
+            currentSession.users().grantToAllUsers(realm1, role1);
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesGrantToAll2) -> {
+            KeycloakSession currentSession = sesGrantToAll2;
+            RealmModel realm1 = currentSession.realms().getRealmByName("realm1");
+
+            RoleModel role1 = realm1.getRole("role1");
+            UserModel user1 = currentSession.users().getUserByUsername("user1", realm1);
+            UserModel user2 = currentSession.users().getUserByUsername("user2", realm1);
+            Assert.assertTrue(user1.hasRole(role1));
+            Assert.assertTrue(user2.hasRole(role1));
+
+            RealmModel realm2 = currentSession.realms().getRealmByName("realm2");
+            UserModel realm2User1 = currentSession.users().getUserByUsername("user1", realm2);
+            Assert.assertFalse(realm2User1.hasRole(role1));
+
+            currentSession.realms().removeRealm(realm1.getId());
+            currentSession.realms().removeRealm(realm2.getId());
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void testUserNotBefore(KeycloakSession session) throws Exception {
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesUserNotBefore1) -> {
+            KeycloakSession currentSession = sesUserNotBefore1;
+            RealmModel realm = currentSession.realms().createRealm("original");
+
+            UserModel user1 = currentSession.users().addUser(realm, "user1");
+            currentSession.users().setNotBeforeForUser(realm, user1, 10);
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesUserNotBefore2) -> {
+            KeycloakSession currentSession = sesUserNotBefore2;
+            RealmModel realm = currentSession.realms().getRealmByName("original");
+
+            UserModel user1 = currentSession.users().getUserByUsername("user1", realm);
+            int notBefore = currentSession.users().getNotBeforeOfUser(realm, user1);
+            Assert.assertEquals(10, notBefore);
+
+            // Try to update
+            currentSession.users().setNotBeforeForUser(realm, user1, 20);
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesUserNotBefore3) -> {
+            KeycloakSession currentSession = sesUserNotBefore3;
+            RealmModel realm = currentSession.realms().getRealmByName("original");
+
+            UserModel user1 = currentSession.users().getUserByUsername("user1", realm);
+            int notBefore = currentSession.users().getNotBeforeOfUser(realm, user1);
+            Assert.assertEquals(20, notBefore);
+        });
+    }
+
+    public static void assertEquals(UserModel expected, UserModel actual) {
+        Assert.assertEquals(expected.getUsername(), actual.getUsername());
+        Assert.assertEquals(expected.getCreatedTimestamp(), actual.getCreatedTimestamp());
+        Assert.assertEquals(expected.getFirstName(), actual.getFirstName());
+        Assert.assertEquals(expected.getLastName(), actual.getLastName());
+
+        String[] expectedRequiredActions = expected.getRequiredActions().toArray(new String[expected.getRequiredActions().size()]);
+        Arrays.sort(expectedRequiredActions);
+        String[] actualRequiredActions = actual.getRequiredActions().toArray(new String[actual.getRequiredActions().size()]);
+        Arrays.sort(actualRequiredActions);
+
+        Assert.assertArrayEquals(expectedRequiredActions, actualRequiredActions);
+    }
+
+    @Override
+    public void configureTestRealm(RealmRepresentation testRealm) {
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserSessionPersisterProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserSessionPersisterProviderTest.java
new file mode 100644
index 0000000..6f80328
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserSessionPersisterProviderTest.java
@@ -0,0 +1,611 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.model;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.TargetsContainer;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.*;
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.common.util.Time;
+import org.keycloak.models.*;
+import org.keycloak.models.session.UserSessionPersisterProvider;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.services.managers.ClientManager;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
+import org.keycloak.testsuite.arquillian.annotation.ModelTest;
+import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.atomic.AtomicReferenceArray;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.keycloak.testsuite.arquillian.DeploymentTargetModifier.AUTH_SERVER_CURRENT;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UserSessionPersisterProviderTest extends AbstractTestRealmKeycloakTest {
+
+    @Deployment
+    @TargetsContainer(AUTH_SERVER_CURRENT)
+    public static WebArchive deploy() {
+        return RunOnServerDeployment.create(UserResource.class, UserSessionPersisterProvider.class)
+                .addPackages(true,
+                        "org.keycloak.testsuite",
+                        "org.keycloak.testsuite.model");
+    }
+
+    @Before
+    public void before() {
+        testingClient.server().run(session -> {
+            initStuff(session);
+        });
+    }
+
+    public static void initStuff(KeycloakSession session) {
+        RealmModel realm = session.realms().getRealm("test");
+        session.users().addUser(realm, "user1").setEmail("user1@localhost");
+        session.users().addUser(realm, "user2").setEmail("user2@localhost");
+    }
+
+    @After
+    public void after() {
+        testingClient.server().run(session -> {
+            RealmModel realm = session.realms().getRealm("test");
+            session.sessions().removeUserSessions(realm);
+            UserModel user1 = session.users().getUserByUsername("user1", realm);
+            UserModel user2 = session.users().getUserByUsername("user2", realm);
+
+            UserManager um = new UserManager(session);
+            if (user1 != null) {
+                um.removeUser(realm, user1);
+            }
+            if (user2 != null) {
+                um.removeUser(realm, user2);
+            }
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void testPersistenceWithLoad(KeycloakSession session) {
+        int started = Time.currentTime();
+        UserSessionModel[][] origSessions = new UserSessionModel[1][1];
+        final UserSessionModel[] userSession = new UserSessionModel[1];
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionWL) -> {
+            // Create some sessions in infinispan
+            origSessions[0] = createSessions(sessionWL);
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionWL22) -> {
+            // Persist 3 created userSessions and clientSessions as offline
+            RealmModel realm = sessionWL22.realms().getRealm("test");
+            ClientModel testApp = realm.getClientByClientId("test-app");
+            List<UserSessionModel> userSessions = sessionWL22.sessions().getUserSessions(realm, testApp);
+            for (UserSessionModel userSessionLooper : userSessions) {
+                persistUserSession(sessionWL22, userSessionLooper, true);
+            }
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionWL2) -> {
+            // Persist 1 online session
+            RealmModel realm = sessionWL2.realms().getRealm("test");
+            userSession[0] = sessionWL2.sessions().getUserSession(realm, origSessions[0][0].getId());
+            persistUserSession(sessionWL2, userSession[0], false);
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionWL3) -> {// Assert online session
+            RealmModel realm = sessionWL3.realms().getRealm("test");
+            List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(sessionWL3, false, 1, 1, 1);
+            UserSessionProviderTest.assertSession(loadedSessions.get(0), sessionWL3.users().getUserByUsername("user1", realm), "127.0.0.1", started, started, "test-app", "third-party");
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionWL4) -> {
+            // Assert offline sessions
+            RealmModel realm = sessionWL4.realms().getRealm("test");
+            List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(sessionWL4, true, 2, 2, 3);
+            UserSessionProviderTest.assertSessions(loadedSessions, origSessions[0]);
+
+            assertSessionLoaded(loadedSessions, origSessions[0][0].getId(), sessionWL4.users().getUserByUsername("user1", realm), "127.0.0.1", started, started, "test-app", "third-party");
+            assertSessionLoaded(loadedSessions, origSessions[0][1].getId(), sessionWL4.users().getUserByUsername("user1", realm), "127.0.0.2", started, started, "test-app");
+            assertSessionLoaded(loadedSessions, origSessions[0][2].getId(), sessionWL4.users().getUserByUsername("user2", realm), "127.0.0.3", started, started, "test-app");
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void testUpdateAndRemove(KeycloakSession session) {
+        int started = Time.currentTime();
+
+        AtomicReference<UserSessionModel[]> origSessionsAt = new AtomicReference<>();
+        AtomicReference<List<UserSessionModel>> loadedSessionsAt = new AtomicReference<>();
+
+        AtomicReference<UserSessionModel> userSessionAt = new AtomicReference<>();
+        AtomicReference<UserSessionModel> persistedSessionAt = new AtomicReference<>();
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesUpdateRemove1) -> {
+            KeycloakSession currentSession = sesUpdateRemove1;
+
+            // Create some sessions in infinispan
+            UserSessionModel[] origSessions = createSessions(currentSession);
+            origSessionsAt.set(origSessions);
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesUpdateRemove2) -> {
+            KeycloakSession currentSession = sesUpdateRemove2;
+            RealmModel realm = currentSession.realms().getRealm("test");
+            UserSessionModel[] origSessions = origSessionsAt.get();
+
+            // Persist 1 offline session
+            UserSessionModel userSession = currentSession.sessions().getUserSession(realm, origSessions[1].getId());
+            userSessionAt.set(userSession);
+
+            persistUserSession(currentSession, userSession, true);
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesUpdateRemove3) -> {
+            KeycloakSession currentSession = sesUpdateRemove3;
+            RealmModel realm = currentSession.realms().getRealm("test");
+
+            UserSessionPersisterProvider persister = currentSession.getProvider(UserSessionPersisterProvider.class);
+
+            // Load offline session
+            List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(currentSession, true, 10, 1, 1);
+            loadedSessionsAt.set(loadedSessions);
+
+            UserSessionModel persistedSession = loadedSessions.get(0);
+            persistedSessionAt.set(persistedSession);
+
+            UserSessionProviderTest.assertSession(persistedSession, currentSession.users().getUserByUsername("user1", realm), "127.0.0.2", started, started, "test-app");
+
+            // create new clientSession
+            AuthenticatedClientSessionModel clientSession = createClientSession(currentSession, realm.getClientByClientId("third-party"), currentSession.sessions().getUserSession(realm, persistedSession.getId()),
+                    "http://redirect", "state");
+            persister.createClientSession(clientSession, true);
+
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesUpdateRemove4) -> {
+            KeycloakSession currentSession = sesUpdateRemove4;
+            RealmModel realm = currentSession.realms().getRealm("test");
+
+            UserSessionPersisterProvider persister = currentSession.getProvider(UserSessionPersisterProvider.class);
+            UserSessionModel userSession = userSessionAt.get();
+
+            // Remove clientSession
+            persister.removeClientSession(userSession.getId(), realm.getClientByClientId("third-party").getId(), true);
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesUpdateRemove5) -> {
+            KeycloakSession currentSession = sesUpdateRemove5;
+            RealmModel realm = currentSession.realms().getRealm("test");
+
+            UserSessionPersisterProvider persister = currentSession.getProvider(UserSessionPersisterProvider.class);
+            List<UserSessionModel> loadedSessions = loadedSessionsAt.get();
+            UserSessionModel persistedSession = persistedSessionAt.get();
+
+            // Assert clientSession removed
+            loadedSessions = loadPersistedSessionsPaginated(currentSession, true, 10, 1, 1);
+            persistedSession = loadedSessions.get(0);
+            UserSessionProviderTest.assertSession(persistedSession, currentSession.users().getUserByUsername("user1", realm), "127.0.0.2", started, started, "test-app");
+
+            // Remove userSession
+            persister.removeUserSession(persistedSession.getId(), true);
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sesUpdateRemove6) -> {
+            KeycloakSession currentSession = sesUpdateRemove6;
+            // Assert nothing found
+            loadPersistedSessionsPaginated(currentSession, true, 10, 0, 0);
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void testOnRealmRemoved(KeycloakSession session) {
+        AtomicReference<String> userSessionID = new AtomicReference<>();
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionRR1) -> {
+            KeycloakSession currentSession = sessionRR1;
+            RealmModel fooRealm = currentSession.realms().createRealm("foo", "foo");
+
+            fooRealm.addClient("foo-app");
+            currentSession.users().addUser(fooRealm, "user3");
+
+            UserSessionModel userSession = currentSession.sessions().createUserSession(fooRealm, currentSession.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
+            userSessionID.set(userSession.getId());
+
+            createClientSession(currentSession, fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state");
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionRR2) -> {
+            KeycloakSession currentSession = sessionRR2;
+
+            // Persist offline session
+            RealmModel fooRealm = currentSession.realms().getRealm("foo");
+            UserSessionModel userSession = currentSession.sessions().getUserSession(fooRealm, userSessionID.get());
+            persistUserSession(currentSession, userSession, true);
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionRR3) -> {
+            KeycloakSession currentSession = sessionRR3;
+
+            // Assert session was persisted
+            loadPersistedSessionsPaginated(currentSession, true, 10, 1, 1);
+
+            // Remove realm
+            RealmManager realmMgr = new RealmManager(currentSession);
+            realmMgr.removeRealm(realmMgr.getRealm("foo"));
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionRR4) -> {
+            KeycloakSession currentSession = sessionRR4;
+
+            // Assert nothing loaded
+            loadPersistedSessionsPaginated(currentSession, true, 10, 0, 0);
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void testOnClientRemoved(KeycloakSession session) {
+        int started = Time.currentTime();
+        AtomicReference<String> userSessionID = new AtomicReference<>();
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionCR1) -> {
+            KeycloakSession currentSession = sessionCR1;
+            RealmModel fooRealm = currentSession.realms().createRealm("foo", "foo");
+
+            fooRealm.addClient("foo-app");
+            fooRealm.addClient("bar-app");
+            currentSession.users().addUser(fooRealm, "user3");
+
+            UserSessionModel userSession = currentSession.sessions().createUserSession(fooRealm, currentSession.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
+            userSessionID.set(userSession.getId());
+
+            createClientSession(currentSession, fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state");
+            createClientSession(currentSession, fooRealm.getClientByClientId("bar-app"), userSession, "http://redirect", "state");
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionCR2) -> {
+            KeycloakSession currentSession = sessionCR2;
+            RealmModel fooRealm = currentSession.realms().getRealm("foo");
+
+            // Persist offline session
+            UserSessionModel userSession = currentSession.sessions().getUserSession(fooRealm, userSessionID.get());
+            persistUserSession(currentSession, userSession, true);
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionCR3) -> {
+            KeycloakSession currentSession = sessionCR3;
+
+            RealmManager realmMgr = new RealmManager(currentSession);
+            ClientManager clientMgr = new ClientManager(realmMgr);
+            RealmModel fooRealm = realmMgr.getRealm("foo");
+
+            // Assert session was persisted with both clientSessions
+            UserSessionModel persistedSession = loadPersistedSessionsPaginated(currentSession, true, 10, 1, 1).get(0);
+            UserSessionProviderTest.assertSession(persistedSession, currentSession.users().getUserByUsername("user3", fooRealm), "127.0.0.1", started, started, "foo-app", "bar-app");
+
+            // Remove foo-app client
+            ClientModel client = fooRealm.getClientByClientId("foo-app");
+            clientMgr.removeClient(fooRealm, client);
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionCR4) -> {
+            KeycloakSession currentSession = sessionCR4;
+            RealmManager realmMgr = new RealmManager(currentSession);
+            ClientManager clientMgr = new ClientManager(realmMgr);
+            RealmModel fooRealm = realmMgr.getRealm("foo");
+
+            // Assert just one bar-app clientSession persisted now
+            UserSessionModel persistedSession = loadPersistedSessionsPaginated(currentSession, true, 10, 1, 1).get(0);
+            UserSessionProviderTest.assertSession(persistedSession, currentSession.users().getUserByUsername("user3", fooRealm), "127.0.0.1", started, started, "bar-app");
+
+            // Remove bar-app client
+            ClientModel client = fooRealm.getClientByClientId("bar-app");
+            clientMgr.removeClient(fooRealm, client);
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionCR5) -> {
+            KeycloakSession currentSession = sessionCR5;
+
+            // Assert loading still works - last userSession is still there, but no clientSession on it
+            loadPersistedSessionsPaginated(currentSession, true, 10, 1, 1);
+
+            // Cleanup
+            RealmManager realmMgr = new RealmManager(currentSession);
+            realmMgr.removeRealm(realmMgr.getRealm("foo"));
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void testOnUserRemoved(KeycloakSession session) {
+        int started = Time.currentTime();
+        AtomicReference<UserSessionModel[]> origSessionsAt = new AtomicReference<>();
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionOR1) -> {
+            KeycloakSession currentSession = sessionOR1;
+
+            // Create some sessions in infinispan
+            UserSessionModel[] origSessions = createSessions(currentSession);
+            origSessionsAt.set(origSessions);
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionOR2) -> {
+            KeycloakSession currentSession = sessionOR2;
+            RealmModel realm = currentSession.realms().getRealm("test");
+
+            UserSessionModel[] origSessions = origSessionsAt.get();
+
+            // Persist 2 offline sessions of 2 users
+            UserSessionModel userSession1 = currentSession.sessions().getUserSession(realm, origSessions[1].getId());
+            UserSessionModel userSession2 = currentSession.sessions().getUserSession(realm, origSessions[2].getId());
+            persistUserSession(currentSession, userSession1, true);
+            persistUserSession(currentSession, userSession2, true);
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionOR3) -> {
+            KeycloakSession currentSession = sessionOR3;
+            RealmModel realm = currentSession.realms().getRealm("test");
+
+            // Load offline sessions
+            loadPersistedSessionsPaginated(currentSession, true, 10, 1, 2);
+
+            // Properly delete user and assert his offlineSession removed
+            UserModel user1 = currentSession.users().getUserByUsername("user1", realm);
+            new UserManager(currentSession).removeUser(realm, user1);
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionOR4) -> {
+            KeycloakSession currentSession = sessionOR4;
+            RealmModel realm = currentSession.realms().getRealm("test");
+
+            UserSessionPersisterProvider persister = currentSession.getProvider(UserSessionPersisterProvider.class);
+            Assert.assertEquals(1, persister.getUserSessionsCount(true));
+
+            List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(currentSession, true, 10, 1, 1);
+            UserSessionModel persistedSession = loadedSessions.get(0);
+            UserSessionProviderTest.assertSession(persistedSession, currentSession.users().getUserByUsername("user2", realm), "127.0.0.3", started, started, "test-app");
+
+            // KEYCLOAK-2431 Assert that userSessionPersister is resistent even to situation, when users are deleted "directly".
+            // No exception will happen. However session will be still there
+            UserModel user2 = currentSession.users().getUserByUsername("user2", realm);
+            currentSession.users().removeUser(realm, user2);
+
+            loadedSessions = loadPersistedSessionsPaginated(currentSession, true, 10, 1, 1);
+
+            // Cleanup
+            UserSessionModel userSession = loadedSessions.get(0);
+            currentSession.sessions().removeUserSession(realm, userSession);
+            persister.removeUserSession(userSession.getId(), userSession.isOffline());
+        });
+    }
+
+    // KEYCLOAK-1999
+    @Test
+    @ModelTest
+    public void testNoSessions(KeycloakSession session) {
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionNS) -> {
+            UserSessionPersisterProvider persister = sessionNS.getProvider(UserSessionPersisterProvider.class);
+            List<UserSessionModel> sessions = persister.loadUserSessions(0, 1, true, 0, "abc");
+            Assert.assertEquals(0, sessions.size());
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void testMoreSessions(KeycloakSession session) {
+        AtomicReference<List<UserSessionModel>> userSessionsAt = new AtomicReference<>();
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionMS1) -> {
+            KeycloakSession currentSession = sessionMS1;
+            RealmModel realm = currentSession.realms().getRealm("test");
+
+            // Create 10 userSessions - each having 1 clientSession
+            List<UserSessionModel> userSessions = new ArrayList<>();
+            UserModel user = currentSession.users().getUserByUsername("user1", realm);
+
+            for (int i = 0; i < 20; i++) {
+                // Having different offsets for each session (to ensure that lastSessionRefresh is also different)
+                Time.setOffset(i);
+
+                UserSessionModel userSession = currentSession.sessions().createUserSession(realm, user, "user1", "127.0.0.1", "form", true, null, null);
+                createClientSession(currentSession, realm.getClientByClientId("test-app"), userSession, "http://redirect", "state");
+                userSessions.add(userSession);
+            }
+
+            userSessionsAt.set(userSessions);
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionMS2) -> {
+            KeycloakSession currentSession = sessionMS2;
+            RealmModel realm = currentSession.realms().getRealm("test");
+
+            List<UserSessionModel> userSessions = userSessionsAt.get();
+
+            for (UserSessionModel userSession : userSessions) {
+                UserSessionModel userSession2 = currentSession.sessions().getUserSession(realm, userSession.getId());
+                persistUserSession(currentSession, userSession2, true);
+            }
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionMS3) -> {
+            KeycloakSession currentSession = sessionMS3;
+            RealmModel realm = currentSession.realms().getRealm("test");
+
+            List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(currentSession, true, 2, 10, 20);
+            UserModel user = currentSession.users().getUserByUsername("user1", realm);
+            ClientModel testApp = realm.getClientByClientId("test-app");
+
+            for (UserSessionModel loadedSession : loadedSessions) {
+                assertEquals(user.getId(), loadedSession.getUser().getId());
+                assertEquals("127.0.0.1", loadedSession.getIpAddress());
+                assertEquals(user.getUsername(), loadedSession.getLoginUsername());
+
+                assertEquals(1, loadedSession.getAuthenticatedClientSessions().size());
+                assertTrue(loadedSession.getAuthenticatedClientSessions().containsKey(testApp.getId()));
+            }
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void testExpiredSessions(KeycloakSession session) {
+        UserSessionModel[][] origSessions = {new UserSessionModel[1]};
+        int started = Time.currentTime();
+        final UserSessionModel[] userSession1 = {null};
+        final UserSessionModel[] userSession2 = {null};
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionES) -> {
+            // Create some sessions in infinispan
+            UserSessionPersisterProvider persister = sessionES.getProvider(UserSessionPersisterProvider.class);
+            origSessions[0] = createSessions(sessionES);
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionES2) -> {
+            // Persist 2 offline sessions of 2 users
+            RealmModel realm = sessionES2.realms().getRealm("test");
+            UserSessionPersisterProvider persister = sessionES2.getProvider(UserSessionPersisterProvider.class);
+            userSession1[0] = sessionES2.sessions().getUserSession(realm, origSessions[0][1].getId());
+            userSession2[0] = sessionES2.sessions().getUserSession(realm, origSessions[0][2].getId());
+            persistUserSession(sessionES2, userSession1[0], true);
+            persistUserSession(sessionES2, userSession2[0], true);
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionES3) -> {
+            // Update one of the sessions with lastSessionRefresh of 20 days ahead
+            int lastSessionRefresh = Time.currentTime() + 1728000;
+            RealmModel realm = sessionES3.realms().getRealm("test");
+            UserSessionPersisterProvider persister = sessionES3.getProvider(UserSessionPersisterProvider.class);
+
+            persister.updateLastSessionRefreshes(realm, lastSessionRefresh, Collections.singleton(userSession1[0].getId()), true);
+
+            // Increase time offset - 40 days
+            Time.setOffset(3456000);
+            try {
+                // Run expiration thread
+                persister.removeExpired(realm);
+
+                // Test the updated session is still in persister. Not updated session is not there anymore
+                List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(sessionES3, true, 10, 1, 1);
+                UserSessionModel persistedSession = loadedSessions.get(0);
+                UserSessionProviderTest.assertSession(persistedSession, sessionES3.users().getUserByUsername("user1", realm), "127.0.0.2", started, lastSessionRefresh, "test-app");
+
+            } finally {
+                // Cleanup
+                Time.setOffset(0);
+            }
+        });
+    }
+
+    private AuthenticatedClientSessionModel createClientSession(KeycloakSession session, ClientModel client, UserSessionModel userSession, String redirect, String state) {
+        RealmModel realm = session.realms().getRealm("test");
+        AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, client, userSession);
+        clientSession.setRedirectUri(redirect);
+        if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
+        return clientSession;
+    }
+
+    private UserSessionModel[] createSessions(KeycloakSession session) {
+        RealmModel realm = session.realms().getRealm("test");
+        UserSessionModel[] sessions = new UserSessionModel[3];
+        sessions[0] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null);
+
+        createClientSession(session, realm.getClientByClientId("test-app"), sessions[0], "http://redirect", "state");
+        createClientSession(session, realm.getClientByClientId("third-party"), sessions[0], "http://redirect", "state");
+
+        sessions[1] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true, null, null);
+        createClientSession(session, realm.getClientByClientId("test-app"), sessions[1], "http://redirect", "state");
+
+        sessions[2] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user2", realm), "user2", "127.0.0.3", "form", true, null, null);
+        createClientSession(session, realm.getClientByClientId("test-app"), sessions[2], "http://redirect", "state");
+
+        return sessions;
+    }
+
+    private void persistUserSession(KeycloakSession session, UserSessionModel userSession, boolean offline) {
+        UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
+        persister.createUserSession(userSession, offline);
+        for (AuthenticatedClientSessionModel clientSession : userSession.getAuthenticatedClientSessions().values()) {
+            persister.createClientSession(clientSession, offline);
+        }
+    }
+
+    public static void assertSessionLoaded(List<UserSessionModel> sessions, String id, UserModel user, String ipAddress, int started, int lastRefresh, String... clients) {
+        for (UserSessionModel session : sessions) {
+            if (session.getId().equals(id)) {
+                UserSessionProviderTest.assertSession(session, user, ipAddress, started, lastRefresh, clients);
+                return;
+            }
+        }
+        Assert.fail("Session with ID " + id + " not found in the list");
+    }
+
+    private List<UserSessionModel> loadPersistedSessionsPaginated(KeycloakSession session, boolean offline, int sessionsPerPage, int expectedPageCount, int expectedSessionsCount) {
+        UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
+
+        int count = persister.getUserSessionsCount(offline);
+
+        int pageCount = 0;
+        boolean next = true;
+        List<UserSessionModel> result = new ArrayList<>();
+        int lastCreatedOn = 0;
+        String lastSessionId = "abc";
+
+        while (next) {
+            List<UserSessionModel> sess = persister.loadUserSessions(0, sessionsPerPage, offline, lastCreatedOn, lastSessionId);
+
+            if (sess.size() < sessionsPerPage) {
+                next = false;
+
+                // We had at least some session
+                if (sess.size() > 0) {
+                    pageCount++;
+                }
+            } else {
+                pageCount++;
+
+                UserSessionModel lastSession = sess.get(sess.size() - 1);
+                lastCreatedOn = lastSession.getStarted();
+                lastSessionId = lastSession.getId();
+            }
+
+            result.addAll(sess);
+        }
+
+        Assert.assertEquals(expectedPageCount, pageCount);
+        Assert.assertEquals(expectedSessionsCount, result.size());
+        return result;
+    }
+
+    @Override
+    public void configureTestRealm(RealmRepresentation testRealm) {
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserSessionProviderOfflineTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserSessionProviderOfflineTest.java
new file mode 100644
index 0000000..b29028d
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/UserSessionProviderOfflineTest.java
@@ -0,0 +1,636 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.model;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.TargetsContainer;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.common.util.Time;
+import org.keycloak.models.*;
+import org.keycloak.models.session.UserSessionPersisterProvider;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.services.managers.ClientManager;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.services.managers.UserSessionManager;
+import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
+import org.keycloak.testsuite.arquillian.annotation.ModelTest;
+import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
+
+import java.util.*;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.keycloak.testsuite.arquillian.DeploymentTargetModifier.AUTH_SERVER_CURRENT;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UserSessionProviderOfflineTest extends AbstractTestRealmKeycloakTest {
+    private static KeycloakSession currentSession;
+    private static RealmModel realm;
+    private static UserSessionManager sessionManager;
+    private static UserSessionPersisterProvider persister;
+
+    @Deployment
+    @TargetsContainer(AUTH_SERVER_CURRENT)
+    public static WebArchive deploy() {
+        return RunOnServerDeployment.create(UserResource.class, UserSessionProviderOfflineTest.class)
+                .addPackages(true,
+                        "org.keycloak.testsuite",
+                        "org.keycloak.testsuite.model");
+    }
+
+    @Before
+    public void before() {
+        testingClient.server().run(session -> {
+            KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionBefore) -> {
+                reloadState(sessionBefore, true);
+                persister = sessionBefore.getProvider(UserSessionPersisterProvider.class);
+            });
+        });
+    }
+
+    @After
+    public void after() {
+        testingClient.server().run(session -> {
+            RealmModel realm = session.realms().getRealmByName("test");
+            session.sessions().removeUserSessions(realm);
+            UserModel user1 = session.users().getUserByUsername("user1", realm);
+            UserModel user2 = session.users().getUserByUsername("user2", realm);
+
+            UserManager um = new UserManager(session);
+            if (user1 != null) {
+                um.removeUser(realm, user1);
+            }
+            if (user2 != null) {
+                um.removeUser(realm, user2);
+            }
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void testOfflineSessionsCrud(KeycloakSession session) {
+        Map<String, Set<String>> offlineSessions = new HashMap<>();
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionCrud) -> {
+            // Create some online sessions in infinispan
+            reloadState(sessionCrud);
+            createSessions(sessionCrud);
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionCrud2) -> {
+            currentSession = sessionCrud2;
+            realm = currentSession.realms().getRealm("test");
+            sessionManager = new UserSessionManager(currentSession);
+            persister = currentSession.getProvider(UserSessionPersisterProvider.class);
+
+            // Key is userSession ID, values are client UUIDS
+            // Persist 3 created userSessions and clientSessions as offline
+            ClientModel testApp = realm.getClientByClientId("test-app");
+            List<UserSessionModel> userSessions = currentSession.sessions().getUserSessions(realm, testApp);
+            for (UserSessionModel userSession : userSessions) {
+                offlineSessions.put(userSession.getId(), createOfflineSessionIncludeClientSessions(currentSession, userSession));
+            }
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionCrud3) -> {
+            currentSession = sessionCrud3;
+            realm = currentSession.realms().getRealm("test");
+            sessionManager = new UserSessionManager(currentSession);
+            persister = currentSession.getProvider(UserSessionPersisterProvider.class);
+
+            // Assert all previously saved offline sessions found
+            for (Map.Entry<String, Set<String>> entry : offlineSessions.entrySet()) {
+                UserSessionModel offlineSession = sessionManager.findOfflineUserSession(realm, entry.getKey());
+                Assert.assertNotNull(offlineSession);
+                Assert.assertEquals(offlineSession.getAuthenticatedClientSessions().keySet(), entry.getValue());
+            }
+
+            // Find clients with offline token
+            UserModel user1 = currentSession.users().getUserByUsername("user1", realm);
+
+            Set<ClientModel> clients = sessionManager.findClientsWithOfflineToken(realm, user1);
+            Assert.assertEquals(clients.size(), 2);
+            for (ClientModel client : clients) {
+                Assert.assertTrue(client.getClientId().equals("test-app") || client.getClientId().equals("third-party"));
+            }
+
+            UserModel user2 = currentSession.users().getUserByUsername("user2", realm);
+
+            clients = sessionManager.findClientsWithOfflineToken(realm, user2);
+            Assert.assertEquals(clients.size(), 1);
+            Assert.assertEquals("test-app", clients.iterator().next().getClientId());
+
+            // Test count
+            ClientModel testApp = realm.getClientByClientId("test-app");
+            ClientModel thirdparty = realm.getClientByClientId("third-party");
+            Assert.assertEquals(3, currentSession.sessions().getOfflineSessionsCount(realm, testApp));
+            Assert.assertEquals(1, currentSession.sessions().getOfflineSessionsCount(realm, thirdparty));
+            // Revoke "test-app" for user1
+            sessionManager.revokeOfflineToken(user1, testApp);
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionCrud4) -> {
+            currentSession = sessionCrud4;
+            realm = currentSession.realms().getRealm("test");
+            sessionManager = new UserSessionManager(currentSession);
+            persister = currentSession.getProvider(UserSessionPersisterProvider.class);
+
+            // Assert userSession revoked
+            ClientModel testApp = realm.getClientByClientId("test-app");
+            ClientModel thirdparty = realm.getClientByClientId("third-party");
+
+            // Still 2 sessions. The count of sessions by client may not be accurate after revoke due the
+            // performance optimizations (the "127.0.0.1" currentSession still has another client "thirdparty" in it)
+            Assert.assertEquals(2, currentSession.sessions().getOfflineSessionsCount(realm, testApp));
+            Assert.assertEquals(1, currentSession.sessions().getOfflineSessionsCount(realm, thirdparty));
+
+            List<UserSessionModel> thirdpartySessions = currentSession.sessions().getOfflineUserSessions(realm, thirdparty, 0, 10);
+            Assert.assertEquals(1, thirdpartySessions.size());
+            Assert.assertEquals("127.0.0.1", thirdpartySessions.get(0).getIpAddress());
+            Assert.assertEquals("user1", thirdpartySessions.get(0).getUser().getUsername());
+
+            UserModel user1 = currentSession.users().getUserByUsername("user1", realm);
+            UserModel user2 = currentSession.users().getUserByUsername("user2", realm);
+
+            Set<ClientModel> clients = sessionManager.findClientsWithOfflineToken(realm, user1);
+            Assert.assertEquals(1, clients.size());
+            Assert.assertEquals("third-party", clients.iterator().next().getClientId());
+            clients = sessionManager.findClientsWithOfflineToken(realm, user2);
+            Assert.assertEquals(1, clients.size());
+            Assert.assertEquals("test-app", clients.iterator().next().getClientId());
+
+            // Revoke the second currentSession for user1 too.
+            sessionManager.revokeOfflineToken(user1, thirdparty);
+
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionCrud5) -> {
+            currentSession = sessionCrud5;
+            realm = currentSession.realms().getRealm("test");
+            sessionManager = new UserSessionManager(currentSession);
+            persister = currentSession.getProvider(UserSessionPersisterProvider.class);
+
+            ClientModel testApp = realm.getClientByClientId("test-app");
+            ClientModel thirdparty = realm.getClientByClientId("third-party");
+
+            // Accurate count now. All sessions of user1 cleared
+            Assert.assertEquals(1, currentSession.sessions().getOfflineSessionsCount(realm, testApp));
+            Assert.assertEquals(0, currentSession.sessions().getOfflineSessionsCount(realm, thirdparty));
+
+            List<UserSessionModel> testAppSessions = currentSession.sessions().getOfflineUserSessions(realm, testApp, 0, 10);
+
+            Assert.assertEquals(1, testAppSessions.size());
+            Assert.assertEquals("127.0.0.3", testAppSessions.get(0).getIpAddress());
+            Assert.assertEquals("user2", testAppSessions.get(0).getUser().getUsername());
+
+            UserModel user1 = currentSession.users().getUserByUsername("user1", realm);
+
+            Set<ClientModel> clients = sessionManager.findClientsWithOfflineToken(realm, user1);
+            Assert.assertEquals(0, clients.size());
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void testOnRealmRemoved(KeycloakSession session) {
+        AtomicReference<String> userSessionID = new AtomicReference<>();
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionRR1) -> {
+            currentSession = sessionRR1;
+            persister = currentSession.getProvider(UserSessionPersisterProvider.class);
+            RealmModel fooRealm = currentSession.realms().createRealm("foo", "foo");
+            fooRealm.addClient("foo-app");
+            currentSession.users().addUser(fooRealm, "user3");
+
+            UserSessionModel userSession = currentSession.sessions().createUserSession(fooRealm, currentSession.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
+            userSessionID.set(userSession.getId());
+
+            createClientSession(currentSession, fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state");
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionRR2) -> {
+            currentSession = sessionRR2;
+            sessionManager = new UserSessionManager(currentSession);
+
+            // Persist offline session
+            RealmModel fooRealm = currentSession.realms().getRealm("foo");
+            UserSessionModel userSession = currentSession.sessions().getUserSession(fooRealm, userSessionID.get());
+            createOfflineSessionIncludeClientSessions(currentSession, userSession);
+
+            UserSessionModel offlineUserSession = sessionManager.findOfflineUserSession(fooRealm, userSession.getId());
+            Assert.assertEquals(offlineUserSession.getAuthenticatedClientSessions().size(), 1);
+            AuthenticatedClientSessionModel offlineClientSession = offlineUserSession.getAuthenticatedClientSessions().values().iterator().next();
+            Assert.assertEquals("foo-app", offlineClientSession.getClient().getClientId());
+            Assert.assertEquals("user3", offlineClientSession.getUserSession().getUser().getUsername());
+
+            // Remove realm
+            RealmManager realmMgr = new RealmManager(currentSession);
+            realmMgr.removeRealm(realmMgr.getRealm("foo"));
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionRR3) -> {
+            currentSession = sessionRR3;
+            RealmModel fooRealm = currentSession.realms().createRealm("foo", "foo");
+
+            fooRealm.addClient("foo-app");
+            currentSession.users().addUser(fooRealm, "user3");
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionRR4) -> {
+            currentSession = sessionRR4;
+            RealmModel fooRealm = currentSession.realms().getRealm("foo");
+
+            Assert.assertEquals(0, currentSession.sessions().getOfflineSessionsCount(fooRealm, fooRealm.getClientByClientId("foo-app")));
+
+            // Cleanup
+            RealmManager realmMgr = new RealmManager(currentSession);
+            realmMgr.removeRealm(realmMgr.getRealm("foo"));
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void testOnClientRemoved(KeycloakSession session) {
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionCR) -> {
+            try {
+                int started = Time.currentTime();
+                AtomicReference<String> userSessionID = new AtomicReference<>();
+
+                KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionCR1) -> {
+                    currentSession = sessionCR1;
+                    sessionManager = new UserSessionManager(currentSession);
+                    persister = currentSession.getProvider(UserSessionPersisterProvider.class);
+                    RealmModel fooRealm = currentSession.realms().createRealm("foo", "foo");
+
+                    fooRealm.addClient("foo-app");
+                    fooRealm.addClient("bar-app");
+                    currentSession.users().addUser(fooRealm, "user3");
+
+                    UserSessionModel userSession = currentSession.sessions().createUserSession(fooRealm, currentSession.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
+                    userSessionID.set(userSession.getId());
+
+                    createClientSession(currentSession, fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state");
+                    createClientSession(currentSession, fooRealm.getClientByClientId("bar-app"), userSession, "http://redirect", "state");
+                });
+
+                KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionCR2) -> {
+                    currentSession = sessionCR2;
+                    // Create offline currentSession
+                    RealmModel fooRealm = currentSession.realms().getRealm("foo");
+                    UserSessionModel userSession = currentSession.sessions().getUserSession(fooRealm, userSessionID.get());
+                    createOfflineSessionIncludeClientSessions(currentSession, userSession);
+                });
+
+                KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionCR3) -> {
+                    currentSession = sessionCR3;
+                    RealmManager realmMgr = new RealmManager(currentSession);
+                    ClientManager clientMgr = new ClientManager(realmMgr);
+                    RealmModel fooRealm = realmMgr.getRealm("foo");
+
+                    // Assert currentSession was persisted with both clientSessions
+                    UserSessionModel offlineSession = currentSession.sessions().getOfflineUserSession(fooRealm, userSessionID.get());
+                    assertSession(offlineSession, currentSession.users().getUserByUsername("user3", fooRealm), "127.0.0.1", started, started, "foo-app", "bar-app");
+
+                    // Remove foo-app client
+                    ClientModel client = fooRealm.getClientByClientId("foo-app");
+                    clientMgr.removeClient(fooRealm, client);
+                });
+
+                KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionCR4) -> {
+                    currentSession = sessionCR4;
+                    RealmManager realmMgr = new RealmManager(currentSession);
+                    ClientManager clientMgr = new ClientManager(realmMgr);
+                    RealmModel fooRealm = realmMgr.getRealm("foo");
+
+                    // Assert just one bar-app clientSession persisted now
+                    UserSessionModel offlineSession = currentSession.sessions().getOfflineUserSession(fooRealm, userSessionID.get());
+                    Assert.assertEquals(1, offlineSession.getAuthenticatedClientSessions().size());
+                    Assert.assertEquals("bar-app", offlineSession.getAuthenticatedClientSessions().values().iterator().next().getClient().getClientId());
+
+                    // Remove bar-app client
+                    ClientModel client = fooRealm.getClientByClientId("bar-app");
+                    clientMgr.removeClient(fooRealm, client);
+                });
+
+                KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionCR5) -> {
+                    currentSession = sessionCR5;
+                    // Assert nothing loaded - userSession was removed as well because it was last userSession
+                    RealmManager realmMgr = new RealmManager(currentSession);
+                    RealmModel fooRealm = realmMgr.getRealm("foo");
+                    UserSessionModel offlineSession = currentSession.sessions().getOfflineUserSession(fooRealm, userSessionID.get());
+                    Assert.assertEquals(0, offlineSession.getAuthenticatedClientSessions().size());
+                });
+
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            } finally {
+                KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionTearDown) -> {
+                    currentSession = sessionTearDown;
+                    RealmManager realmMgr = new RealmManager(currentSession);
+                    RealmModel fooRealm = realmMgr.getRealm("foo");
+                    UserModel user3 = currentSession.users().getUserByUsername("user3", fooRealm);
+
+                    // Remove user3
+                    new UserManager(currentSession).removeUser(fooRealm, user3);
+
+                    // Cleanup
+                    realmMgr = new RealmManager(currentSession);
+                    realmMgr.removeRealm(realmMgr.getRealm("foo"));
+                });
+            }
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void testOnUserRemoved(KeycloakSession session) {
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionUR) -> {
+            try {
+                int started = Time.currentTime();
+                AtomicReference<String> userSessionID = new AtomicReference<>();
+
+                KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionUR1) -> {
+                    currentSession = sessionUR1;
+                    RealmModel fooRealm = currentSession.realms().createRealm("foo", "foo");
+                    fooRealm.addClient("foo-app");
+                    currentSession.users().addUser(fooRealm, "user3");
+
+                    UserSessionModel userSession = currentSession.sessions().createUserSession(fooRealm, currentSession.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
+                    userSessionID.set(userSession.getId());
+
+                    createClientSession(currentSession, fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state");
+                });
+
+                KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionUR2) -> {
+                    currentSession = sessionUR2;
+
+                    // Create offline session
+                    RealmModel fooRealm = currentSession.realms().getRealm("foo");
+                    UserSessionModel userSession = currentSession.sessions().getUserSession(fooRealm, userSessionID.get());
+                    createOfflineSessionIncludeClientSessions(currentSession, userSession);
+                });
+
+                KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionUR3) -> {
+                    currentSession = sessionUR3;
+
+                    RealmManager realmMgr = new RealmManager(currentSession);
+                    RealmModel fooRealm = realmMgr.getRealm("foo");
+                    UserModel user3 = currentSession.users().getUserByUsername("user3", fooRealm);
+
+                    // Assert session was persisted with both clientSessions
+                    UserSessionModel offlineSession = currentSession.sessions().getOfflineUserSession(fooRealm, userSessionID.get());
+                    assertSession(offlineSession, user3, "127.0.0.1", started, started, "foo-app");
+                });
+
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            } finally {
+                KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionTearDown) -> {
+                    currentSession = sessionTearDown;
+
+                    RealmManager realmMgr = new RealmManager(currentSession);
+                    RealmModel fooRealm = realmMgr.getRealm("foo");
+                    UserModel user3 = currentSession.users().getUserByUsername("user3", fooRealm);
+
+                    // Remove user3
+                    new UserManager(currentSession).removeUser(fooRealm, user3);
+
+                    // Cleanup
+                    realmMgr = new RealmManager(currentSession);
+                    realmMgr.removeRealm(realmMgr.getRealm("foo"));
+                });
+            }
+        });
+    }
+
+    @Test
+    @ModelTest
+    public void testExpired(KeycloakSession session) {
+        AtomicReference<UserSessionModel[]> origSessionsAt = new AtomicReference<>();
+
+        // Key is userSessionId, value is set of client UUIDS
+        Map<String, Set<String>> offlineSessions = new HashMap<>();
+        ClientModel[] testApp = new ClientModel[1];
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionExpired1) -> {
+            // Create some online sessions in infinispan
+            currentSession = sessionExpired1;
+            reloadState(currentSession);
+            UserSessionModel[] origSessions = createSessions(currentSession);
+            origSessionsAt.set(origSessions);
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionExpired2) -> {
+            currentSession = sessionExpired2;
+            realm = currentSession.realms().getRealm("test");
+            sessionManager = new UserSessionManager(currentSession);
+            persister = currentSession.getProvider(UserSessionPersisterProvider.class);
+
+            // Persist 3 created userSessions and clientSessions as offline
+            testApp[0] = realm.getClientByClientId("test-app");
+            List<UserSessionModel> userSessions = currentSession.sessions().getUserSessions(realm, testApp[0]);
+            for (UserSessionModel userSession : userSessions) {
+                offlineSessions.put(userSession.getId(), createOfflineSessionIncludeClientSessions(currentSession, userSession));
+            }
+
+            // Assert all previously saved offline sessions found
+            for (Map.Entry<String, Set<String>> entry : offlineSessions.entrySet()) {
+                UserSessionModel foundSession = sessionManager.findOfflineUserSession(realm, entry.getKey());
+                Assert.assertEquals(foundSession.getAuthenticatedClientSessions().keySet(), entry.getValue());
+            }
+        });
+
+        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionExpired3) -> {
+            currentSession = sessionExpired3;
+            realm = currentSession.realms().getRealm("test");
+            persister = currentSession.getProvider(UserSessionPersisterProvider.class);
+            UserSessionModel[] origSessions = origSessionsAt.get();
+
+            UserSessionModel session0 = currentSession.sessions().getOfflineUserSession(realm, origSessions[0].getId());
+            Assert.assertNotNull(session0);
+
+            // sessions are in persister too
+            Assert.assertEquals(3, persister.getUserSessionsCount(true));
+
+            Time.setOffset(300);
+
+            // Set lastSessionRefresh to currentSession[0] to 0
+            session0.setLastSessionRefresh(Time.currentTime());
+        });
+
+        try {
+            KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionExpired4) -> {
+                currentSession = sessionExpired4;
+                realm = currentSession.realms().getRealm("test");
+                UserSessionModel[] origSessions = origSessionsAt.get();
+                // Increase timeOffset - 20 days
+                Time.setOffset(1728000);
+
+                UserSessionModel session0 = currentSession.sessions().getOfflineUserSession(realm, origSessions[0].getId());
+                session0.setLastSessionRefresh(Time.currentTime());
+            });
+
+            KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionExpired5) -> {
+                currentSession = sessionExpired5;
+                realm = currentSession.realms().getRealm("test");
+                persister = currentSession.getProvider(UserSessionPersisterProvider.class);
+
+                // Increase timeOffset - 40 days
+                Time.setOffset(3456000);
+
+                // Expire and ensure that all sessions despite session0 were removed
+                currentSession.sessions().removeExpired(realm);
+                persister.removeExpired(realm);
+            });
+
+            KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionExpired6) -> {
+                currentSession = sessionExpired6;
+                realm = currentSession.realms().getRealm("test");
+                persister = currentSession.getProvider(UserSessionPersisterProvider.class);
+                UserSessionModel[] origSessions = origSessionsAt.get();
+
+                // assert session0 is the only session found
+                Assert.assertNotNull(currentSession.sessions().getOfflineUserSession(realm, origSessions[0].getId()));
+                Assert.assertNull(currentSession.sessions().getOfflineUserSession(realm, origSessions[1].getId()));
+                Assert.assertNull(currentSession.sessions().getOfflineUserSession(realm, origSessions[2].getId()));
+
+                Assert.assertEquals(1, persister.getUserSessionsCount(true));
+
+                // Expire everything and assert nothing found
+                Time.setOffset(6000000);
+
+                currentSession.sessions().removeExpired(realm);
+                persister.removeExpired(realm);
+
+            });
+
+            KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession sessionExpired7) -> {
+                currentSession = sessionExpired7;
+                realm = currentSession.realms().getRealm("test");
+                sessionManager = new UserSessionManager(currentSession);
+                persister = currentSession.getProvider(UserSessionPersisterProvider.class);
+
+                for (String userSessionId : offlineSessions.keySet()) {
+                    Assert.assertNull(sessionManager.findOfflineUserSession(realm, userSessionId));
+                }
+                Assert.assertEquals(0, persister.getUserSessionsCount(true));
+            });
+
+        } finally {
+            Time.setOffset(0);
+        }
+    }
+
+
+    private static Set<String> createOfflineSessionIncludeClientSessions(KeycloakSession session, UserSessionModel
+            userSession) {
+        Set<String> offlineSessions = new HashSet<>();
+        UserSessionManager localManager = new UserSessionManager(session);
+        for (AuthenticatedClientSessionModel clientSession : userSession.getAuthenticatedClientSessions().values()) {
+            localManager.createOrUpdateOfflineSession(clientSession, userSession);
+            offlineSessions.add(clientSession.getClient().getId());
+        }
+
+        return offlineSessions;
+    }
+
+    public static void assertSession(UserSessionModel session, UserModel user, String ipAddress, int started,
+                                     int lastRefresh, String... clients) {
+        assertEquals(user.getId(), session.getUser().getId());
+        assertEquals(ipAddress, session.getIpAddress());
+        assertEquals(user.getUsername(), session.getLoginUsername());
+        assertEquals("form", session.getAuthMethod());
+        assertTrue(session.isRememberMe());
+        assertTrue((session.getStarted() >= started - 1) && (session.getStarted() <= started + 1));
+        assertTrue((session.getLastSessionRefresh() >= lastRefresh - 1) && (session.getLastSessionRefresh() <= lastRefresh + 1));
+
+        String[] actualClients = new String[session.getAuthenticatedClientSessions().size()];
+        int i = 0;
+        for (Map.Entry<String, AuthenticatedClientSessionModel> entry : session.getAuthenticatedClientSessions().entrySet()) {
+            String clientUUID = entry.getKey();
+            AuthenticatedClientSessionModel clientSession = entry.getValue();
+            Assert.assertEquals(clientUUID, clientSession.getClient().getId());
+            actualClients[i] = clientSession.getClient().getClientId();
+            i++;
+        }
+    }
+
+    private static AuthenticatedClientSessionModel createClientSession(KeycloakSession sessionParam, ClientModel
+            client, UserSessionModel userSession, String redirect, String state) {
+        AuthenticatedClientSessionModel clientSession = sessionParam.sessions().createClientSession(client.getRealm(), client, userSession);
+        clientSession.setRedirectUri(redirect);
+        if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
+        return clientSession;
+    }
+
+    private static UserSessionModel[] createSessions(KeycloakSession session) {
+        UserSessionModel[] sessions = new UserSessionModel[3];
+        sessions[0] = session.sessions().createUserSession(realm, currentSession.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null);
+
+        Set<String> roles = new HashSet<String>();
+        roles.add("one");
+        roles.add("two");
+
+        Set<String> protocolMappers = new HashSet<String>();
+        protocolMappers.add("mapper-one");
+        protocolMappers.add("mapper-two");
+
+        createClientSession(session, realm.getClientByClientId("test-app"), sessions[0], "http://redirect", "state");
+        createClientSession(session, realm.getClientByClientId("third-party"), sessions[0], "http://redirect", "state");
+
+        sessions[1] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true, null, null);
+        createClientSession(session, realm.getClientByClientId("test-app"), sessions[1], "http://redirect", "state");
+
+        sessions[2] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user2", realm), "user2", "127.0.0.3", "form", true, null, null);
+        createClientSession(session, realm.getClientByClientId("test-app"), sessions[2], "http://redirect", "state");
+
+        return sessions;
+    }
+
+    public static void reloadState(KeycloakSession session) {
+        reloadState(session, false);
+    }
+
+    public static void reloadState(KeycloakSession session, Boolean initialConfig) {
+        currentSession = session;
+        realm = currentSession.realms().getRealm("test");
+        if (initialConfig) {
+            currentSession.users().addUser(realm, "user1").setEmail("user1@localhost");
+            currentSession.users().addUser(realm, "user2").setEmail("user2@localhost");
+        }
+        sessionManager = new UserSessionManager(currentSession);
+        persister = currentSession.getProvider(UserSessionPersisterProvider.class);
+    }
+
+    @Override
+    public void configureTestRealm(RealmRepresentation testRealm) {
+
+    }
+}