keycloak-aplcache

Details

diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
index 7c7679f..4cfea1d 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
@@ -26,6 +26,7 @@ import org.keycloak.common.util.Time;
 import org.keycloak.models.AuthenticatedClientSessionModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.OfflineUserSessionModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserLoginFailureModel;
 import org.keycloak.models.UserModel;
@@ -849,12 +850,22 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
         entity.setBrokerSessionId(userSession.getBrokerSessionId());
         entity.setBrokerUserId(userSession.getBrokerUserId());
         entity.setIpAddress(userSession.getIpAddress());
-        entity.setLoginUsername(userSession.getLoginUsername());
         entity.setNotes(userSession.getNotes() == null ? new ConcurrentHashMap<>() : userSession.getNotes());
         entity.setAuthenticatedClientSessions(new AuthenticatedClientSessionStore());
         entity.setRememberMe(userSession.isRememberMe());
         entity.setState(userSession.getState());
-        entity.setUser(userSession.getUser().getId());
+        if (userSession instanceof OfflineUserSessionModel) {
+            // this is a hack so that UserModel doesn't have to be available when offline token is imported.
+            // see related JIRA - KEYCLOAK-5350 and corresponding test
+            OfflineUserSessionModel oline = (OfflineUserSessionModel)userSession;
+            entity.setUser(oline.getUserId());
+            // NOTE: Hack
+            // We skip calling entity.setLoginUsername(userSession.getLoginUsername())
+
+        } else {
+            entity.setLoginUsername(userSession.getLoginUsername());
+            entity.setUser(userSession.getUser().getId());
+        }
 
         entity.setStarted(userSession.getStarted());
         entity.setLastSessionRefresh(userSession.getLastSessionRefresh());
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java
index 9e58a2f..e62fdd9 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java
@@ -176,7 +176,13 @@ public class UserSessionAdapter implements UserSessionModel {
 
     @Override
     public String getLoginUsername() {
-        return entity.getLoginUsername();
+        if (entity.getLoginUsername() == null) {
+            // this is a hack so that UserModel doesn't have to be available when offline token is imported.
+            // see related JIRA - KEYCLOAK-5350 and corresponding test
+            return getUser().getUsername();
+        } else {
+            return entity.getLoginUsername();
+        }
     }
 
     public String getIpAddress() {
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProvider.java
index dd49136..8d07942 100644
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProvider.java
@@ -17,6 +17,7 @@
 
 package org.keycloak.models.jpa.session;
 
+import org.jboss.logging.Logger;
 import org.keycloak.models.AuthenticatedClientSessionModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
@@ -42,6 +43,7 @@ import java.util.Map;
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
 public class JpaUserSessionPersisterProvider implements UserSessionPersisterProvider {
+    private static final Logger logger = Logger.getLogger(JpaUserSessionPersisterProvider.class);
 
     private final KeycloakSession session;
     private final EntityManager em;
@@ -205,15 +207,19 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
         List<String> userSessionIds = new ArrayList<>();
         for (PersistentUserSessionEntity entity : results) {
             RealmModel realm = session.realms().getRealm(entity.getRealmId());
-            UserModel user = session.users().getUserById(entity.getUserId(), realm);
-
-            // Case when user was deleted in the meantime
-            if (user == null) {
-                onUserRemoved(realm, entity.getUserId());
-                return loadUserSessions(firstResult, maxResults, offline);
+            try {
+                UserModel user = session.users().getUserById(entity.getUserId(), realm);
+                // Case when user was deleted in the meantime
+                if (user == null) {
+                    onUserRemoved(realm, entity.getUserId());
+                    return loadUserSessions(firstResult, maxResults, offline);
+                }
+            } catch (Exception e) {
+                logger.debugv(e,"Failed to load user with id {0}", entity.getUserId());
             }
 
-            result.add(toAdapter(realm, user, entity));
+
+            result.add(toAdapter(realm, entity));
             userSessionIds.add(entity.getUserSessionId());
         }
 
@@ -247,7 +253,7 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
         return result;
     }
 
-    private PersistentUserSessionAdapter toAdapter(RealmModel realm, UserModel user, PersistentUserSessionEntity entity) {
+    private PersistentUserSessionAdapter toAdapter(RealmModel realm, PersistentUserSessionEntity entity) {
         PersistentUserSessionModel model = new PersistentUserSessionModel();
         model.setUserSessionId(entity.getUserSessionId());
         model.setLastSessionRefresh(entity.getLastSessionRefresh());
@@ -255,7 +261,7 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
         model.setOffline(offlineFromString(entity.getOffline()));
 
         Map<String, AuthenticatedClientSessionModel> clientSessions = new HashMap<>();
-        return new PersistentUserSessionAdapter(model, realm, user, clientSessions);
+        return new PersistentUserSessionAdapter(session, model, realm, entity.getUserId(), clientSessions);
     }
 
     private PersistentAuthenticatedClientSessionAdapter toAdapter(RealmModel realm, PersistentUserSessionAdapter userSession, PersistentClientSessionEntity entity) {
@@ -264,7 +270,7 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
         PersistentClientSessionModel model = new PersistentClientSessionModel();
         model.setClientId(entity.getClientId());
         model.setUserSessionId(userSession.getId());
-        model.setUserId(userSession.getUser().getId());
+        model.setUserId(userSession.getUserId());
         model.setTimestamp(entity.getTimestamp());
         model.setData(entity.getData());
         return new PersistentAuthenticatedClientSessionAdapter(model, realm, client, userSession);
diff --git a/server-spi-private/src/main/java/org/keycloak/models/OfflineUserSessionModel.java b/server-spi-private/src/main/java/org/keycloak/models/OfflineUserSessionModel.java
new file mode 100644
index 0000000..7ffae7d
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/OfflineUserSessionModel.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.models;
+
+/**
+ * Hacked extension to UserSessionModel so that user id can be obtain directly so
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface OfflineUserSessionModel extends UserSessionModel {
+    public String getUserId();
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/session/PersistentUserSessionAdapter.java b/server-spi-private/src/main/java/org/keycloak/models/session/PersistentUserSessionAdapter.java
index 87df79f..e650fee 100644
--- a/server-spi-private/src/main/java/org/keycloak/models/session/PersistentUserSessionAdapter.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/session/PersistentUserSessionAdapter.java
@@ -19,7 +19,9 @@ package org.keycloak.models.session;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 import org.keycloak.models.AuthenticatedClientSessionModel;
+import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ModelException;
+import org.keycloak.models.OfflineUserSessionModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
@@ -33,11 +35,14 @@ import java.util.Map;
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
-public class PersistentUserSessionAdapter implements UserSessionModel {
+public class PersistentUserSessionAdapter implements OfflineUserSessionModel {
 
     private final PersistentUserSessionModel model;
-    private final UserModel user;
+    private UserModel user;
+    private String userId;
+    private String username;
     private final RealmModel realm;
+    private KeycloakSession session;
     private final Map<String, AuthenticatedClientSessionModel> authenticatedClientSessions;
 
     private PersistentUserSessionData data;
@@ -60,14 +65,16 @@ public class PersistentUserSessionAdapter implements UserSessionModel {
         this.model.setLastSessionRefresh(other.getLastSessionRefresh());
 
         this.user = other.getUser();
+        this.userId = this.user.getId();
         this.realm = other.getRealm();
         this.authenticatedClientSessions = other.getAuthenticatedClientSessions();
     }
 
-    public PersistentUserSessionAdapter(PersistentUserSessionModel model, RealmModel realm, UserModel user, Map<String, AuthenticatedClientSessionModel> clientSessions) {
+    public PersistentUserSessionAdapter(KeycloakSession session, PersistentUserSessionModel model, RealmModel realm, String userId, Map<String, AuthenticatedClientSessionModel> clientSessions) {
+        this.session = session;
         this.model = model;
         this.realm = realm;
-        this.user = user;
+        this.userId = userId;
         this.authenticatedClientSessions = clientSessions;
     }
 
@@ -113,17 +120,25 @@ public class PersistentUserSessionAdapter implements UserSessionModel {
 
     @Override
     public UserModel getUser() {
+        if (user == null) {
+            user = session.users().getUserById(userId, realm);
+        }
         return user;
     }
 
     @Override
+    public String getUserId() {
+        return userId;
+    }
+
+    @Override
     public RealmModel getRealm() {
         return realm;
     }
 
     @Override
     public String getLoginUsername() {
-        return user.getUsername();
+        return getUser().getUsername();
     }
 
     @Override
diff --git a/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/AssertEvents.java b/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/AssertEvents.java
index 02486fb..a42f576 100755
--- a/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/AssertEvents.java
+++ b/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/AssertEvents.java
@@ -349,6 +349,13 @@ public class AssertEvents implements TestRule, EventListenerProviderFactory {
                 throw new AssertionError("No type received within timeout");
             }
         }
+        public Event event() {
+            try {
+                return events.poll(10, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                throw new AssertionError("No type received within timeout");
+            }
+        }
 
         public Event assertEvent(Event actual) {
             if (expected.getError() != null && !expected.getType().toString().endsWith("_ERROR")) {
diff --git a/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/federation/storage/FailableHardcodedStorageProvider.java b/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/federation/storage/FailableHardcodedStorageProvider.java
new file mode 100644
index 0000000..780d1c7
--- /dev/null
+++ b/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/federation/storage/FailableHardcodedStorageProvider.java
@@ -0,0 +1,209 @@
+/*
+ * 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.federation.storage;
+
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.credential.CredentialInput;
+import org.keycloak.credential.CredentialInputUpdater;
+import org.keycloak.credential.CredentialInputValidator;
+import org.keycloak.credential.CredentialModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.UserModelDelegate;
+import org.keycloak.storage.UserStorageProvider;
+import org.keycloak.storage.user.ImportedUserValidation;
+import org.keycloak.storage.user.UserLookupProvider;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class FailableHardcodedStorageProvider implements UserStorageProvider, UserLookupProvider, ImportedUserValidation, CredentialInputUpdater, CredentialInputValidator {
+
+    public static String username = "billb";
+    public static String password = "password";
+    public static String email = "billb@nowhere.com";
+    public static String first = "Bill";
+    public static String last = "Burke";
+    public static MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
+
+    public static boolean fail;
+
+    protected ComponentModel model;
+    protected KeycloakSession session;
+    protected boolean componentFail;
+
+    public FailableHardcodedStorageProvider(ComponentModel model, KeycloakSession session) {
+        this.model = model;
+        this.session = session;
+        componentFail = model.getConfig().getFirst("fail") != null && model.getConfig().getFirst("fail").equalsIgnoreCase("true");
+    }
+
+    @Override
+    public boolean supportsCredentialType(String credentialType) {
+        if (fail || componentFail) throw new RuntimeException("FORCED FAILURE");
+        return CredentialModel.PASSWORD.equals(credentialType);
+    }
+
+    @Override
+    public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
+        if (fail || componentFail) throw new RuntimeException("FORCED FAILURE");
+        if (!(input instanceof UserCredentialModel)) return false;
+        if (!user.getUsername().equals(username)) throw new RuntimeException("UNKNOWN USER!");
+
+        if (input.getType().equals(UserCredentialModel.PASSWORD)) {
+            password = ((UserCredentialModel)input).getValue();
+            return true;
+
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
+        if (fail || componentFail) throw new RuntimeException("FORCED FAILURE");
+
+    }
+
+    @Override
+    public Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) {
+        if (fail || componentFail) throw new RuntimeException("FORCED FAILURE");
+        return Collections.EMPTY_SET;
+    }
+
+    @Override
+    public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
+        if (fail || componentFail) throw new RuntimeException("FORCED FAILURE");
+        return CredentialModel.PASSWORD.equals(credentialType);
+    }
+
+    @Override
+    public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
+        if (fail || componentFail) throw new RuntimeException("FORCED FAILURE");
+        if (!(input instanceof UserCredentialModel)) return false;
+        if (!user.getUsername().equals("billb")) throw new RuntimeException("UNKNOWN USER!");
+        if (input.getType().equals(UserCredentialModel.PASSWORD)) {
+            return password != null && password.equals( ((UserCredentialModel)input).getValue());
+        } else {
+            return false;
+        }
+    }
+
+    private static class Delegate extends UserModelDelegate {
+        public Delegate(UserModel delegate) {
+            super(delegate);
+        }
+
+        @Override
+        public void setUsername(String name) {
+            super.setUsername(name);
+            name = name;
+        }
+
+        @Override
+        public void setSingleAttribute(String name, String value) {
+            super.setSingleAttribute(name, value);
+            attributes.putSingle(name, value);
+        }
+
+        @Override
+        public void setAttribute(String name, List<String> values) {
+            super.setAttribute(name, values);
+            attributes.put(name, values);
+        }
+
+        @Override
+        public void removeAttribute(String name) {
+            super.removeAttribute(name);
+            attributes.remove(name);
+        }
+
+        @Override
+        public void setFirstName(String firstName) {
+            super.setFirstName(firstName);
+            first = firstName;
+        }
+
+        @Override
+        public void setLastName(String lastName) {
+            super.setLastName(lastName);
+            last = lastName;
+        }
+
+        @Override
+        public void setEmail(String em) {
+            super.setEmail(em);
+            email = em;
+        }
+    }
+
+    @Override
+    public UserModel validate(RealmModel realm, UserModel user) {
+        if (fail || componentFail) throw new RuntimeException("FORCED FAILURE");
+        return new Delegate(user);
+    }
+
+    @Override
+    public UserModel getUserById(String id, RealmModel realm) {
+        if (fail || componentFail) throw new RuntimeException("FORCED FAILURE");
+        throw new RuntimeException("THIS IMPORTS  SHOULD NEVER BE CALLED");
+    }
+
+    @Override
+    public UserModel getUserByUsername(String uname, RealmModel realm) {
+        if (fail || componentFail) throw new RuntimeException("FORCED FAILURE");
+        if (!username.equals(uname)) return null;
+        UserModel local = session.userLocalStorage().getUserByUsername(uname, realm);
+        if (local != null && !model.getId().equals(local.getFederationLink())) {
+            throw new RuntimeException("local storage has wrong federation link");
+        }
+        if (local != null) return new Delegate(local);
+        local = session.userLocalStorage().addUser(realm, uname);
+        local.setEnabled(true);
+        local.setFirstName(first);
+        local.setLastName(last);
+        local.setEmail(email);
+        local.setFederationLink(model.getId());
+        for (String key : attributes.keySet()) {
+            List<String> values = attributes.get(key);
+            if (values == null) continue;
+            local.setAttribute(key, values);
+        }
+        return new Delegate(local);
+    }
+
+    @Override
+    public UserModel getUserByEmail(String email, RealmModel realm) {
+        if (fail || componentFail) throw new RuntimeException("FORCED FAILURE");
+        return null;
+    }
+
+    @Override
+    public void close() {
+
+    }
+}
diff --git a/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/federation/storage/FailableHardcodedStorageProviderFactory.java b/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/federation/storage/FailableHardcodedStorageProviderFactory.java
new file mode 100644
index 0000000..46faec0
--- /dev/null
+++ b/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/federation/storage/FailableHardcodedStorageProviderFactory.java
@@ -0,0 +1,55 @@
+/*
+ * 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.federation.storage;
+
+import org.keycloak.component.ComponentModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.storage.UserStorageProviderFactory;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class FailableHardcodedStorageProviderFactory implements UserStorageProviderFactory<FailableHardcodedStorageProvider> {
+
+    public static final String PROVIDER_ID = "failable-hardcoded-storage";
+
+    @Override
+    public FailableHardcodedStorageProvider create(KeycloakSession session, ComponentModel model) {
+        return new FailableHardcodedStorageProvider(model, session);
+    }
+
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+
+    static List<ProviderConfigProperty> OPTIONS = new LinkedList<>();
+    static {
+        ProviderConfigProperty prop = new ProviderConfigProperty("fail", "fail", "If on, provider will throw exception", ProviderConfigProperty.BOOLEAN_TYPE, "false");
+        OPTIONS.add(prop);
+    }
+    @Override
+    public List<ProviderConfigProperty> getConfigProperties() {
+        return OPTIONS;
+    }
+
+}
diff --git a/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/federation/storage/UserStorageFailureTest.java b/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/federation/storage/UserStorageFailureTest.java
new file mode 100644
index 0000000..d8d5fbd
--- /dev/null
+++ b/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/federation/storage/UserStorageFailureTest.java
@@ -0,0 +1,177 @@
+/*
+ * 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.federation.storage;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.common.constants.ServiceAccountConstants;
+import org.keycloak.common.util.Time;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.credential.CredentialAuthentication;
+import org.keycloak.credential.UserCredentialStoreManager;
+import org.keycloak.events.Details;
+import org.keycloak.events.Event;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.cache.CachedUserModel;
+import org.keycloak.models.cache.infinispan.UserAdapter;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.RefreshToken;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.EventRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.storage.StorageId;
+import org.keycloak.storage.UserStorageProviderModel;
+import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.Constants;
+import org.keycloak.testsuite.OAuthClient;
+import org.keycloak.testsuite.pages.AppPage;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.rule.KeycloakRule;
+import org.keycloak.testsuite.rule.WebResource;
+import org.keycloak.testsuite.rule.WebRule;
+import org.openqa.selenium.WebDriver;
+
+import java.util.Calendar;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UserStorageFailureTest {
+    public static ComponentModel memoryProvider = null;
+    @ClassRule
+    public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
+
+        @Override
+        public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+            UserStorageProviderModel model = new UserStorageProviderModel();
+            model.setName("failure");
+            model.setPriority(0);
+            model.setProviderId(FailableHardcodedStorageProviderFactory.PROVIDER_ID);
+            model.setParentId(appRealm.getId());
+            memoryProvider = appRealm.addComponentModel(model);
+
+            ClientModel offlineClient = appRealm.addClient("offline-client");
+            offlineClient.setEnabled(true);
+            offlineClient.setDirectAccessGrantsEnabled(true);
+            offlineClient.setSecret("secret");
+            HashSet<String> redirects = new HashSet<>();
+            redirects.add(Constants.AUTH_SERVER_ROOT + "/offline-client");
+            offlineClient.setRedirectUris(redirects);
+            offlineClient.setServiceAccountsEnabled(true);
+            offlineClient.setFullScopeAllowed(true);
+
+            UserModel serviceAccount = manager.getSession().users().addUser(appRealm, ServiceAccountConstants.SERVICE_ACCOUNT_USER_PREFIX + offlineClient.getClientId());
+            serviceAccount.setEnabled(true);
+            RoleModel role = appRealm.getRole("offline_access");
+            Assert.assertNotNull(role);
+            serviceAccount.grantRole(role);
+            serviceAccount.setServiceAccountClientLink(offlineClient.getClientId());
+
+        }
+    });
+
+    @Rule
+    public WebRule webRule = new WebRule(this);
+
+    @WebResource
+    protected OAuthClient oauth;
+
+    @WebResource
+    protected WebDriver driver;
+
+    @WebResource
+    protected AppPage appPage;
+
+    @WebResource
+    protected LoginPage loginPage;
+
+    @Rule
+    public AssertEvents events = new AssertEvents(keycloakRule);
+
+
+    // this is a hack so that UserModel doesn't have to be available when offline token is imported.
+    // see related JIRA - KEYCLOAK-5350 and corresponding test
+
+    /**
+     *  KEYCLOAK-5350
+     */
+    @Test
+    public void testKeycloak5350() {
+        oauth.scope(OAuth2Constants.OFFLINE_ACCESS);
+        oauth.clientId("offline-client");
+        oauth.redirectUri(Constants.AUTH_SERVER_ROOT + "/offline-client");
+        oauth.doLogin("billb", "password");
+
+        Event loginEvent = events.expectLogin()
+                .client("offline-client")
+                .detail(Details.REDIRECT_URI, Constants.AUTH_SERVER_ROOT + "/offline-client")
+                .event();
+
+        final String sessionId = loginEvent.getSessionId();
+        String codeId = loginEvent.getDetails().get(Details.CODE_ID);
+
+        String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+
+        OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "secret");
+        AccessToken token = oauth.verifyToken(tokenResponse.getAccessToken());
+        String offlineTokenString = tokenResponse.getRefreshToken();
+        RefreshToken offlineToken = oauth.verifyRefreshToken(offlineTokenString);
+        events.clear();
+
+        FailableHardcodedStorageProvider.fail = true;
+        // restart server to make sure we can still boot if user storage is down
+        keycloakRule.restartServer();
+
+        // test that once user storage provider is available again we can still access the token.
+        FailableHardcodedStorageProvider.fail = false;
+        tokenResponse = oauth.doRefreshTokenRequest(offlineTokenString, "secret");
+        Assert.assertNotNull(tokenResponse.getAccessToken());
+        token = oauth.verifyToken(tokenResponse.getAccessToken());
+        offlineTokenString = tokenResponse.getRefreshToken();
+        offlineToken = oauth.verifyRefreshToken(offlineTokenString);
+        events.clear();
+    }
+
+    @After
+    public void resetTimeoffset() {
+        Time.setOffset(0);
+
+    }
+
+    //@Test
+    public void testIDE() throws Exception {
+        Thread.sleep(100000000);
+    }
+
+}
diff --git a/testsuite/integration-deprecated/src/test/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory b/testsuite/integration-deprecated/src/test/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory
index dcc5143..4e0892a 100644
--- a/testsuite/integration-deprecated/src/test/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory
+++ b/testsuite/integration-deprecated/src/test/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory
@@ -1,3 +1,4 @@
 org.keycloak.testsuite.federation.sync.SyncDummyUserFederationProviderFactory
 org.keycloak.testsuite.federation.storage.UserPropertyFileStorageFactory
-org.keycloak.testsuite.federation.storage.UserMapStorageFactory
\ No newline at end of file
+org.keycloak.testsuite.federation.storage.UserMapStorageFactory
+org.keycloak.testsuite.federation.storage.FailableHardcodedStorageProviderFactory
\ No newline at end of file