KEYCLOAK-5350
This commit is contained in:
parent
3a7fd9e732
commit
64f8d7ce25
10 changed files with 533 additions and 19 deletions
|
@ -26,6 +26,7 @@ import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.OfflineUserSessionModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserLoginFailureModel;
|
import org.keycloak.models.UserLoginFailureModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
@ -839,12 +840,22 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
entity.setBrokerSessionId(userSession.getBrokerSessionId());
|
entity.setBrokerSessionId(userSession.getBrokerSessionId());
|
||||||
entity.setBrokerUserId(userSession.getBrokerUserId());
|
entity.setBrokerUserId(userSession.getBrokerUserId());
|
||||||
entity.setIpAddress(userSession.getIpAddress());
|
entity.setIpAddress(userSession.getIpAddress());
|
||||||
entity.setLoginUsername(userSession.getLoginUsername());
|
|
||||||
entity.setNotes(userSession.getNotes() == null ? new ConcurrentHashMap<>() : userSession.getNotes());
|
entity.setNotes(userSession.getNotes() == null ? new ConcurrentHashMap<>() : userSession.getNotes());
|
||||||
entity.setAuthenticatedClientSessions(new AuthenticatedClientSessionStore());
|
entity.setAuthenticatedClientSessions(new AuthenticatedClientSessionStore());
|
||||||
entity.setRememberMe(userSession.isRememberMe());
|
entity.setRememberMe(userSession.isRememberMe());
|
||||||
entity.setState(userSession.getState());
|
entity.setState(userSession.getState());
|
||||||
|
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.setUser(userSession.getUser().getId());
|
||||||
|
}
|
||||||
|
|
||||||
entity.setStarted(userSession.getStarted());
|
entity.setStarted(userSession.getStarted());
|
||||||
entity.setLastSessionRefresh(userSession.getLastSessionRefresh());
|
entity.setLastSessionRefresh(userSession.getLastSessionRefresh());
|
||||||
|
|
|
@ -176,8 +176,14 @@ public class UserSessionAdapter implements UserSessionModel {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getLoginUsername() {
|
public String 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();
|
return entity.getLoginUsername();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public String getIpAddress() {
|
public String getIpAddress() {
|
||||||
return entity.getIpAddress();
|
return entity.getIpAddress();
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package org.keycloak.models.jpa.session;
|
package org.keycloak.models.jpa.session;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
@ -42,6 +43,7 @@ import java.util.Map;
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
public class JpaUserSessionPersisterProvider implements UserSessionPersisterProvider {
|
public class JpaUserSessionPersisterProvider implements UserSessionPersisterProvider {
|
||||||
|
private static final Logger logger = Logger.getLogger(JpaUserSessionPersisterProvider.class);
|
||||||
|
|
||||||
private final KeycloakSession session;
|
private final KeycloakSession session;
|
||||||
private final EntityManager em;
|
private final EntityManager em;
|
||||||
|
@ -205,15 +207,19 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
|
||||||
List<String> userSessionIds = new ArrayList<>();
|
List<String> userSessionIds = new ArrayList<>();
|
||||||
for (PersistentUserSessionEntity entity : results) {
|
for (PersistentUserSessionEntity entity : results) {
|
||||||
RealmModel realm = session.realms().getRealm(entity.getRealmId());
|
RealmModel realm = session.realms().getRealm(entity.getRealmId());
|
||||||
|
try {
|
||||||
UserModel user = session.users().getUserById(entity.getUserId(), realm);
|
UserModel user = session.users().getUserById(entity.getUserId(), realm);
|
||||||
|
|
||||||
// Case when user was deleted in the meantime
|
// Case when user was deleted in the meantime
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
onUserRemoved(realm, entity.getUserId());
|
onUserRemoved(realm, entity.getUserId());
|
||||||
return loadUserSessions(firstResult, maxResults, offline);
|
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());
|
userSessionIds.add(entity.getUserSessionId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,14 +253,14 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private PersistentUserSessionAdapter toAdapter(RealmModel realm, UserModel user, PersistentUserSessionEntity entity) {
|
private PersistentUserSessionAdapter toAdapter(RealmModel realm, PersistentUserSessionEntity entity) {
|
||||||
PersistentUserSessionModel model = new PersistentUserSessionModel();
|
PersistentUserSessionModel model = new PersistentUserSessionModel();
|
||||||
model.setUserSessionId(entity.getUserSessionId());
|
model.setUserSessionId(entity.getUserSessionId());
|
||||||
model.setLastSessionRefresh(entity.getLastSessionRefresh());
|
model.setLastSessionRefresh(entity.getLastSessionRefresh());
|
||||||
model.setData(entity.getData());
|
model.setData(entity.getData());
|
||||||
|
|
||||||
Map<String, AuthenticatedClientSessionModel> clientSessions = new HashMap<>();
|
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) {
|
private PersistentAuthenticatedClientSessionAdapter toAdapter(RealmModel realm, PersistentUserSessionAdapter userSession, PersistentClientSessionEntity entity) {
|
||||||
|
@ -263,7 +269,7 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
|
||||||
PersistentClientSessionModel model = new PersistentClientSessionModel();
|
PersistentClientSessionModel model = new PersistentClientSessionModel();
|
||||||
model.setClientId(entity.getClientId());
|
model.setClientId(entity.getClientId());
|
||||||
model.setUserSessionId(userSession.getId());
|
model.setUserSessionId(userSession.getId());
|
||||||
model.setUserId(userSession.getUser().getId());
|
model.setUserId(userSession.getUserId());
|
||||||
model.setTimestamp(entity.getTimestamp());
|
model.setTimestamp(entity.getTimestamp());
|
||||||
model.setData(entity.getData());
|
model.setData(entity.getData());
|
||||||
return new PersistentAuthenticatedClientSessionAdapter(model, realm, client, userSession);
|
return new PersistentAuthenticatedClientSessionAdapter(model, realm, client, userSession);
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
|
@ -19,7 +19,9 @@ package org.keycloak.models.session;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.ModelException;
|
import org.keycloak.models.ModelException;
|
||||||
|
import org.keycloak.models.OfflineUserSessionModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
@ -33,11 +35,14 @@ import java.util.Map;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @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 PersistentUserSessionModel model;
|
||||||
private final UserModel user;
|
private UserModel user;
|
||||||
|
private String userId;
|
||||||
|
private String username;
|
||||||
private final RealmModel realm;
|
private final RealmModel realm;
|
||||||
|
private KeycloakSession session;
|
||||||
private final Map<String, AuthenticatedClientSessionModel> authenticatedClientSessions;
|
private final Map<String, AuthenticatedClientSessionModel> authenticatedClientSessions;
|
||||||
|
|
||||||
private PersistentUserSessionData data;
|
private PersistentUserSessionData data;
|
||||||
|
@ -60,14 +65,16 @@ public class PersistentUserSessionAdapter implements UserSessionModel {
|
||||||
this.model.setLastSessionRefresh(other.getLastSessionRefresh());
|
this.model.setLastSessionRefresh(other.getLastSessionRefresh());
|
||||||
|
|
||||||
this.user = other.getUser();
|
this.user = other.getUser();
|
||||||
|
this.userId = this.user.getId();
|
||||||
this.realm = other.getRealm();
|
this.realm = other.getRealm();
|
||||||
this.authenticatedClientSessions = other.getAuthenticatedClientSessions();
|
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.model = model;
|
||||||
this.realm = realm;
|
this.realm = realm;
|
||||||
this.user = user;
|
this.userId = userId;
|
||||||
this.authenticatedClientSessions = clientSessions;
|
this.authenticatedClientSessions = clientSessions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,9 +120,17 @@ public class PersistentUserSessionAdapter implements UserSessionModel {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserModel getUser() {
|
public UserModel getUser() {
|
||||||
|
if (user == null) {
|
||||||
|
user = session.users().getUserById(userId, realm);
|
||||||
|
}
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RealmModel getRealm() {
|
public RealmModel getRealm() {
|
||||||
return realm;
|
return realm;
|
||||||
|
@ -123,7 +138,7 @@ public class PersistentUserSessionAdapter implements UserSessionModel {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getLoginUsername() {
|
public String getLoginUsername() {
|
||||||
return user.getUsername();
|
return getUser().getUsername();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -349,6 +349,13 @@ public class AssertEvents implements TestRule, EventListenerProviderFactory {
|
||||||
throw new AssertionError("No type received within timeout");
|
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) {
|
public Event assertEvent(Event actual) {
|
||||||
if (expected.getError() != null && !expected.getType().toString().endsWith("_ERROR")) {
|
if (expected.getError() != null && !expected.getType().toString().endsWith("_ERROR")) {
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
org.keycloak.testsuite.federation.sync.SyncDummyUserFederationProviderFactory
|
org.keycloak.testsuite.federation.sync.SyncDummyUserFederationProviderFactory
|
||||||
org.keycloak.testsuite.federation.storage.UserPropertyFileStorageFactory
|
org.keycloak.testsuite.federation.storage.UserPropertyFileStorageFactory
|
||||||
org.keycloak.testsuite.federation.storage.UserMapStorageFactory
|
org.keycloak.testsuite.federation.storage.UserMapStorageFactory
|
||||||
|
org.keycloak.testsuite.federation.storage.FailableHardcodedStorageProviderFactory
|
Loading…
Reference in a new issue