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 eb49db4b2b..eb687a6c55 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;
@@ -839,12 +840,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 d0c2a4a804..a79d6fd58b 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 64246e8d6d..fd18852eef 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 Marek Posolda
*/
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 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,14 +253,14 @@ 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());
model.setData(entity.getData());
Map 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) {
@@ -263,7 +269,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 0000000000..7ffae7d7d6
--- /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 Bill Burke
+ * @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 095a85782c..7a328543fa 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 Marek Posolda
*/
-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 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 clientSessions) {
+ public PersistentUserSessionAdapter(KeycloakSession session, PersistentUserSessionModel model, RealmModel realm, String userId, Map clientSessions) {
+ this.session = session;
this.model = model;
this.realm = realm;
- this.user = user;
+ this.userId = userId;
this.authenticatedClientSessions = clientSessions;
}
@@ -113,9 +120,17 @@ 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;
@@ -123,7 +138,7 @@ public class PersistentUserSessionAdapter implements UserSessionModel {
@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 02486fba52..a42f576d66 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 0000000000..780d1c7e13
--- /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 Bill Burke
+ * @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 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 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 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 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 0000000000..46faec0244
--- /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 Bill Burke
+ * @version $Revision: 1 $
+ */
+public class FailableHardcodedStorageProviderFactory implements UserStorageProviderFactory {
+
+ 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 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 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 0000000000..d8d5fbd87a
--- /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 Bill Burke
+ * @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 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 dcc5143025..4e0892aa13 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