diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java
index c6bfd63b19..ec6025b2f8 100755
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java
@@ -1,27 +1,54 @@
package org.keycloak.models.sessions.infinispan;
import org.infinispan.Cache;
+import org.infinispan.Version;
+import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.UserSessionProvider;
import org.keycloak.models.UserSessionProviderFactory;
+import org.keycloak.models.sessions.infinispan.compat.MemUserSessionProviderFactory;
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
/**
+ * Uses Infinispan to store user sessions. On EAP 6.4 (Infinispan 5.2) map reduce is not supported for local caches as a work around
+ * the old memory user session provider is used in this case. This can be removed once we drop support for EAP 6.4.
+ *
* @author Stian Thorgersen
*/
public class InfinispanUserSessionProviderFactory implements UserSessionProviderFactory {
+ private static final Logger log = Logger.getLogger(InfinispanUserSessionProviderFactory.class);
+
+ private Boolean compatMode;
+ private MemUserSessionProviderFactory compatProviderFactory;
+
@Override
public UserSessionProvider create(KeycloakSession session) {
- InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
- Cache cache = connections.getCache(InfinispanConnectionProvider.SESSION_CACHE_NAME);
- Cache loginFailures = connections.getCache(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME);
- return new InfinispanUserSessionProvider(session, cache, loginFailures);
+ if (compatMode == null) {
+ synchronized (this) {
+ if (compatMode == null) {
+ compatMode = isCompatMode(session);
+ if (compatMode) {
+ compatProviderFactory = new MemUserSessionProviderFactory();
+ log.info("Infinispan version doesn't support map reduce for local cache. Falling back to deprecated mem user session provider.");
+ }
+ }
+ }
+ }
+
+ if (!compatMode) {
+ InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
+ Cache cache = connections.getCache(InfinispanConnectionProvider.SESSION_CACHE_NAME);
+ Cache loginFailures = connections.getCache(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME);
+ return new InfinispanUserSessionProvider(session, cache, loginFailures);
+ } else {
+ return compatProviderFactory.create(session);
+ }
}
@Override
@@ -35,6 +62,9 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
@Override
public void close() {
+ if (compatProviderFactory != null) {
+ compatProviderFactory.close();
+ }
}
@Override
@@ -42,5 +72,16 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
return "infinispan";
}
+ private static boolean isCompatMode(KeycloakSession session) {
+ if (Version.getVersionShort() < Version.getVersionShort("5.3.0.Final")) {
+ InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
+ Cache cache = connections.getCache(InfinispanConnectionProvider.SESSION_CACHE_NAME);
+ if (cache.getAdvancedCache().getRpcManager() == null) {
+ return true;
+ }
+ }
+ return false;
+ }
+
}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/ClientSessionAdapter.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/ClientSessionAdapter.java
new file mode 100755
index 0000000000..1d806c14d1
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/ClientSessionAdapter.java
@@ -0,0 +1,200 @@
+package org.keycloak.models.sessions.infinispan.compat;
+
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.sessions.infinispan.compat.entities.ClientSessionEntity;
+import org.keycloak.models.sessions.infinispan.compat.entities.UserSessionEntity;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Stian Thorgersen
+ */
+public class ClientSessionAdapter implements ClientSessionModel {
+
+ private KeycloakSession session;
+ private MemUserSessionProvider provider;
+ private RealmModel realm;
+ private ClientSessionEntity entity;
+
+ public ClientSessionAdapter(KeycloakSession session, MemUserSessionProvider provider, RealmModel realm, ClientSessionEntity entity) {
+ this.session = session;
+ this.provider = provider;
+ this.realm = realm;
+ this.entity = entity;
+ }
+
+ @Override
+ public String getId() {
+ return entity.getId();
+ }
+
+ @Override
+ public RealmModel getRealm() {
+ return session.realms().getRealm(entity.getRealmId());
+ }
+
+ public ClientSessionEntity getEntity() {
+ return entity;
+ }
+
+ @Override
+ public ClientModel getClient() {
+ return realm.getClientById(entity.getClientId());
+ }
+
+ @Override
+ public UserSessionModel getUserSession() {
+ if (entity.getSession() == null) return null;
+ return new UserSessionAdapter(session, provider, realm, entity.getSession());
+ }
+
+ @Override
+ public void setUserSession(UserSessionModel userSession) {
+ if (userSession == null) {
+ if (entity.getSession() != null) {
+ entity.getSession().getClientSessions().remove(entity);
+ }
+ entity.setSession(null);
+ } else {
+ UserSessionAdapter adapter = (UserSessionAdapter) userSession;
+ UserSessionEntity userSessionEntity = adapter.getEntity();
+ entity.setSession(userSessionEntity);
+ userSessionEntity.getClientSessions().add(entity);
+ }
+ }
+
+ @Override
+ public void setRedirectUri(String uri) {
+ entity.setRedirectUri(uri);
+ }
+
+ @Override
+ public void setRoles(Set roles) {
+ entity.setRoles(roles);
+ }
+
+ @Override
+ public String getRedirectUri() {
+ return entity.getRedirectUri();
+ }
+
+ @Override
+ public int getTimestamp() {
+ return entity.getTimestamp();
+ }
+
+ @Override
+ public void setTimestamp(int timestamp) {
+ entity.setTimestamp(timestamp);
+ }
+
+ @Override
+ public String getAction() {
+ return entity.getAction();
+ }
+
+ @Override
+ public void setAction(String action) {
+ entity.setAction(action);
+ }
+
+ @Override
+ public Set getRoles() {
+ return entity.getRoles();
+ }
+
+ @Override
+ public Set getProtocolMappers() {
+ return entity.getProtocolMappers();
+ }
+
+ @Override
+ public void setProtocolMappers(Set protocolMappers) {
+ entity.setProtocolMappers(protocolMappers);
+ }
+
+ @Override
+ public String getNote(String name) {
+ return entity.getNotes().get(name);
+ }
+
+ @Override
+ public void setNote(String name, String value) {
+ entity.getNotes().put(name, value);
+
+ }
+
+ @Override
+ public void removeNote(String name) {
+ entity.getNotes().remove(name);
+
+ }
+
+ @Override
+ public Map getNotes() {
+ if (entity.getNotes() == null || entity.getNotes().isEmpty()) return Collections.emptyMap();
+ Map copy = new HashMap<>();
+ copy.putAll(entity.getNotes());
+ return copy;
+ }
+
+ @Override
+ public void setUserSessionNote(String name, String value) {
+ entity.getUserSessionNotes().put(name, value);
+ }
+
+ @Override
+ public Map getUserSessionNotes() {
+ return entity.getUserSessionNotes();
+ }
+
+ @Override
+ public String getAuthMethod() {
+ return entity.getAuthMethod();
+ }
+
+ @Override
+ public void setAuthMethod(String method) {
+ entity.setAuthMethod(method);
+ }
+
+ @Override
+ public Map getExecutionStatus() {
+ return entity.getAuthenticatorStatus();
+ }
+
+ @Override
+ public void setExecutionStatus(String authenticator, ExecutionStatus status) {
+ entity.getAuthenticatorStatus().put(authenticator, status);
+
+ }
+
+ @Override
+ public void clearExecutionStatus() {
+ entity.getAuthenticatorStatus().clear();
+ }
+
+ @Override
+ public void clearUserSessionNotes() {
+ entity.getUserSessionNotes().clear();
+ }
+
+ @Override
+ public UserModel getAuthenticatedUser() {
+ return entity.getAuthUserId() == null ? null : session.users().getUserById(entity.getAuthUserId(), realm); }
+
+ @Override
+ public void setAuthenticatedUser(UserModel user) {
+ if (user == null) entity.setAuthUserId(null);
+ else entity.setAuthUserId(user.getId());
+
+ }
+}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProvider.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProvider.java
new file mode 100755
index 0000000000..947c605663
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProvider.java
@@ -0,0 +1,382 @@
+package org.keycloak.models.sessions.infinispan.compat;
+
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.UserSessionProvider;
+import org.keycloak.models.UsernameLoginFailureModel;
+import org.keycloak.models.sessions.infinispan.compat.entities.ClientSessionEntity;
+import org.keycloak.models.sessions.infinispan.compat.entities.UserSessionEntity;
+import org.keycloak.models.sessions.infinispan.compat.entities.UsernameLoginFailureEntity;
+import org.keycloak.models.sessions.infinispan.compat.entities.UsernameLoginFailureKey;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.RealmInfoUtil;
+import org.keycloak.util.Time;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author Stian Thorgersen
+ */
+public class MemUserSessionProvider implements UserSessionProvider {
+
+ private final KeycloakSession session;
+ private final ConcurrentHashMap userSessions;
+ private final ConcurrentHashMap userSessionsByBrokerSessionId;
+ private final ConcurrentHashMap> userSessionsByBrokerUserId;
+ private final ConcurrentHashMap clientSessions;
+ private final ConcurrentHashMap loginFailures;
+
+ public MemUserSessionProvider(KeycloakSession session, ConcurrentHashMap userSessions, ConcurrentHashMap userSessionsByBrokerSessionId, ConcurrentHashMap> userSessionsByBrokerUserId, ConcurrentHashMap clientSessions, ConcurrentHashMap loginFailures) {
+ this.session = session;
+ this.userSessions = userSessions;
+ this.clientSessions = clientSessions;
+ this.loginFailures = loginFailures;
+ this.userSessionsByBrokerSessionId = userSessionsByBrokerSessionId;
+ this.userSessionsByBrokerUserId = userSessionsByBrokerUserId;
+ }
+
+ @Override
+ public ClientSessionModel createClientSession(RealmModel realm, ClientModel client) {
+ ClientSessionEntity entity = new ClientSessionEntity();
+ entity.setId(KeycloakModelUtils.generateId());
+ entity.setTimestamp(Time.currentTime());
+ entity.setClientId(client.getId());
+ entity.setRealmId(realm.getId());
+ clientSessions.put(entity.getId(), entity);
+ return new ClientSessionAdapter(session, this, realm, entity);
+ }
+
+ @Override
+ public void removeClientSession(RealmModel realm, ClientSessionModel clientSession) {
+ ClientSessionEntity entity = ((ClientSessionAdapter)clientSession).getEntity();
+ UserSessionModel userSession = clientSession.getUserSession();
+ if (userSession != null) {
+ UserSessionEntity userSessionEntity = ((UserSessionAdapter)userSession).getEntity();
+ userSessionEntity.getClientSessions().remove(entity);
+ }
+ clientSessions.remove(clientSession.getId());
+ }
+
+ @Override
+ public ClientSessionModel getClientSession(RealmModel realm, String id) {
+ ClientSessionEntity entity = clientSessions.get(id);
+ return entity != null ? new ClientSessionAdapter(session, this, realm, entity) : null;
+ }
+
+ @Override
+ public ClientSessionModel getClientSession(String id) {
+ ClientSessionEntity entity = clientSessions.get(id);
+ if (entity != null) {
+ RealmModel realm = session.realms().getRealm(entity.getRealmId());
+ return new ClientSessionAdapter(session, this, realm, entity);
+ }
+ return null;
+ }
+
+ @Override
+ public UserSessionModel createUserSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId) {
+ String id = KeycloakModelUtils.generateId();
+
+ UserSessionEntity entity = new UserSessionEntity();
+ entity.setId(id);
+ entity.setRealm(realm.getId());
+ entity.setUser(user.getId());
+ entity.setLoginUsername(loginUsername);
+ entity.setIpAddress(ipAddress);
+ entity.setAuthMethod(authMethod);
+ entity.setRememberMe(rememberMe);
+
+ int currentTime = Time.currentTime();
+
+ entity.setStarted(currentTime);
+ entity.setLastSessionRefresh(currentTime);
+ entity.setBrokerSessionId(brokerSessionId);
+ entity.setBrokerUserId(brokerUserId);
+
+ userSessions.put(id, entity);
+ if (brokerSessionId != null) {
+ userSessionsByBrokerSessionId.put(brokerSessionId, id);
+ }
+ if (brokerUserId != null) {
+ while (true) { // while loop gets around a race condition when a user session is removed
+ Set set = userSessionsByBrokerUserId.get(brokerUserId);
+ if (set == null) {
+ Set value = new HashSet<>();
+ set = userSessionsByBrokerUserId.putIfAbsent(brokerUserId, value);
+ if (set == null) {
+ set = value;
+ }
+ }
+ synchronized (set) {
+ set.add(id);
+ }
+ if (userSessionsByBrokerUserId.get(brokerUserId) == set) {
+ // we are ensured set isn't deleted before the new id is added
+ break;
+ }
+ }
+ }
+
+ return new UserSessionAdapter(session, this, realm, entity);
+ }
+
+ @Override
+ public List getUserSessionByBrokerUserId(RealmModel realm, String brokerUserId) {
+ Set sessions = userSessionsByBrokerUserId.get(brokerUserId);
+ if (sessions == null) return Collections.emptyList();
+ List userSessions = new LinkedList();
+ for (String id : sessions) {
+ UserSessionModel userSession = getUserSession(realm, id);
+ if (userSession != null) userSessions.add(userSession);
+ }
+ return userSessions;
+ }
+
+ @Override
+ public UserSessionModel getUserSessionByBrokerSessionId(RealmModel realm, String brokerSessionId) {
+ String id = userSessionsByBrokerSessionId.get(brokerSessionId);
+ if (id == null) return null;
+ return getUserSession(realm, id);
+ }
+
+ @Override
+ public UserSessionModel getUserSession(RealmModel realm, String id) {
+ UserSessionEntity entity = getUserSessionEntity(realm, id);
+ return entity != null ? new UserSessionAdapter(session, this, realm, entity) : null;
+ }
+
+ UserSessionEntity getUserSessionEntity(RealmModel realm, String id) {
+ UserSessionEntity entity = userSessions.get(id);
+ if (entity != null && entity.getRealm().equals(realm.getId())) {
+ return entity;
+ }
+ return null;
+ }
+
+ @Override
+ public List getUserSessions(RealmModel realm, UserModel user) {
+ List userSessions = new LinkedList();
+ for (UserSessionEntity s : this.userSessions.values()) {
+ if (s.getRealm().equals(realm.getId()) && s.getUser().equals(user.getId())) {
+ userSessions.add(new UserSessionAdapter(session, this, realm, s));
+ }
+ }
+ return userSessions;
+ }
+
+ @Override
+ public List getUserSessionsByNote(RealmModel realm, String noteName, String noteValue) {
+ List userSessions = new LinkedList();
+ for (UserSessionEntity s : this.userSessions.values()) {
+ if (s.getRealm().equals(realm.getId()) && noteValue.equals(s.getNotes().get(noteName))) {
+ userSessions.add(new UserSessionAdapter(session, this, realm, s));
+ }
+ }
+ return userSessions;
+ }
+
+ @Override
+ public List getUserSessions(RealmModel realm, ClientModel client) {
+ List userSessionEntities = new LinkedList();
+ for (ClientSessionEntity s : clientSessions.values()) {
+ String realmId = realm.getId();
+ String clientId = client.getId();
+ if (s.getSession() != null && s.getSession().getRealm().equals(realmId) && s.getClientId().equals(clientId)) {
+ if (!userSessionEntities.contains(s.getSession())) {
+ userSessionEntities.add(s.getSession());
+ }
+ }
+ }
+
+ List userSessions = new LinkedList();
+ for (UserSessionEntity e : userSessionEntities) {
+ userSessions.add(new UserSessionAdapter(session, this, realm, e));
+ }
+ Collections.sort(userSessions, new UserSessionSort());
+ return userSessions;
+ }
+
+ @Override
+ public List getUserSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
+ List userSessions = getUserSessions(realm, client);
+ if (firstResult > userSessions.size()) {
+ return Collections.emptyList();
+ }
+
+ int toIndex = (firstResult + maxResults) < userSessions.size() ? firstResult + maxResults : userSessions.size();
+ return userSessions.subList(firstResult, toIndex);
+ }
+
+ @Override
+ public int getActiveUserSessions(RealmModel realm, ClientModel client) {
+ return getUserSessions(realm, client).size();
+ }
+
+ @Override
+ public void removeUserSession(RealmModel realm, UserSessionModel session) {
+ UserSessionEntity entity = getUserSessionEntity(realm, session.getId());
+ if (entity != null) {
+ userSessions.remove(entity.getId());
+ remove(entity);
+ }
+ }
+
+ @Override
+ public void removeUserSessions(RealmModel realm, UserModel user) {
+ Iterator itr = userSessions.values().iterator();
+ while (itr.hasNext()) {
+ UserSessionEntity s = itr.next();
+ if (s.getRealm().equals(realm.getId()) && s.getUser().equals(user.getId())) {
+ itr.remove();
+ remove(s);
+ }
+ }
+ }
+
+ protected void remove(UserSessionEntity s) {
+ if (s.getBrokerSessionId() != null) {
+ userSessionsByBrokerSessionId.remove(s.getBrokerSessionId());
+ }
+ if (s.getBrokerUserId() != null) {
+ Set set = userSessionsByBrokerUserId.get(s.getBrokerUserId());
+ if (set != null) {
+ synchronized (set) {
+ set.remove(s.getId());
+ // this is a race condition :(
+ // Since it will be very rare for a user to have concurrent sessions, I'm hoping we never hit this
+ if (set.isEmpty()) userSessionsByBrokerUserId.remove(s.getBrokerUserId());
+ }
+ }
+ }
+ for (ClientSessionEntity clientSession : s.getClientSessions()) {
+ clientSessions.remove(clientSession.getId());
+ }
+ }
+
+ @Override
+ public void removeExpiredUserSessions(RealmModel realm) {
+ Iterator itr = userSessions.values().iterator();
+ while (itr.hasNext()) {
+ UserSessionEntity s = itr.next();
+ if (s.getRealm().equals(realm.getId()) && (s.getLastSessionRefresh() < Time.currentTime() - realm.getSsoSessionIdleTimeout() || s.getStarted() < Time.currentTime() - realm.getSsoSessionMaxLifespan())) {
+ itr.remove();
+
+ remove(s);
+ }
+ }
+ int expired = Time.currentTime() - RealmInfoUtil.getDettachedClientSessionLifespan(realm);
+ Iterator citr = clientSessions.values().iterator();
+ while (citr.hasNext()) {
+ ClientSessionEntity c = citr.next();
+ if (c.getSession() == null && c.getRealmId().equals(realm.getId()) && c.getTimestamp() < expired) {
+ citr.remove();
+ }
+ }
+ }
+
+ @Override
+ public void removeUserSessions(RealmModel realm) {
+ Iterator itr = userSessions.values().iterator();
+ while (itr.hasNext()) {
+ UserSessionEntity s = itr.next();
+ if (s.getRealm().equals(realm.getId())) {
+ itr.remove();
+
+ remove(s);
+ }
+ }
+ Iterator citr = clientSessions.values().iterator();
+ while (citr.hasNext()) {
+ ClientSessionEntity c = citr.next();
+ if (c.getSession() == null && c.getRealmId().equals(realm.getId())) {
+ citr.remove();
+ }
+ }
+ }
+
+ @Override
+ public UsernameLoginFailureModel getUserLoginFailure(RealmModel realm, String username) {
+ UsernameLoginFailureEntity entity = loginFailures.get(new UsernameLoginFailureKey(realm.getId(), username));
+ return entity != null ? new UsernameLoginFailureAdapter(entity) : null;
+ }
+
+ @Override
+ public UsernameLoginFailureModel addUserLoginFailure(RealmModel realm, String username) {
+ UsernameLoginFailureKey key = new UsernameLoginFailureKey(realm.getId(), username);
+ UsernameLoginFailureEntity entity = new UsernameLoginFailureEntity(username, realm.getId());
+ if (loginFailures.putIfAbsent(key, entity) != null) {
+ throw new ModelDuplicateException();
+ }
+ return new UsernameLoginFailureAdapter(entity);
+ }
+
+ @Override
+ public void removeUserLoginFailure(RealmModel realm, String username) {
+ loginFailures.remove(new UsernameLoginFailureKey(realm.getId(), username));
+ }
+
+ @Override
+ public void removeAllUserLoginFailures(RealmModel realm) {
+ Iterator itr = loginFailures.values().iterator();
+ while (itr.hasNext()) {
+ if (itr.next().getRealm().equals(realm.getId())) {
+ itr.remove();
+ }
+ }
+
+ }
+
+ @Override
+ public void onRealmRemoved(RealmModel realm) {
+ removeUserSessions(realm);
+ removeAllUserLoginFailures(realm);
+ }
+
+ @Override
+ public void onClientRemoved(RealmModel realm, ClientModel client) {
+ for (ClientSessionEntity e : clientSessions.values()) {
+ if (e.getRealmId().equals(realm.getId()) && e.getClientId().equals(client.getId())) {
+ clientSessions.remove(e.getId());
+ e.getSession().removeClientSession(e);
+ }
+ }
+ }
+
+ @Override
+ public void onUserRemoved(RealmModel realm, UserModel user) {
+ removeUserSessions(realm, user);
+
+ loginFailures.remove(new UsernameLoginFailureKey(realm.getId(), user.getUsername()));
+ loginFailures.remove(new UsernameLoginFailureKey(realm.getId(), user.getEmail()));
+ }
+
+ @Override
+ public void close() {
+ }
+
+ private class UserSessionSort implements Comparator {
+
+ @Override
+ public int compare(UserSessionModel o1, UserSessionModel o2) {
+ int r = o1.getStarted() - o2.getStarted();
+ if (r == 0) {
+ return o1.getId().compareTo(o2.getId());
+ } else {
+ return r;
+ }
+ }
+ }
+
+}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProviderFactory.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProviderFactory.java
new file mode 100644
index 0000000000..6a84b1a6e5
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProviderFactory.java
@@ -0,0 +1,40 @@
+package org.keycloak.models.sessions.infinispan.compat;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.UserSessionProvider;
+import org.keycloak.models.UserSessionProviderFactory;
+import org.keycloak.models.sessions.infinispan.compat.entities.ClientSessionEntity;
+import org.keycloak.models.sessions.infinispan.compat.entities.UserSessionEntity;
+import org.keycloak.models.sessions.infinispan.compat.entities.UsernameLoginFailureEntity;
+import org.keycloak.models.sessions.infinispan.compat.entities.UsernameLoginFailureKey;
+
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author Stian Thorgersen
+ */
+public class MemUserSessionProviderFactory {
+
+ private ConcurrentHashMap userSessions = new ConcurrentHashMap();
+
+ private ConcurrentHashMap clientSessions = new ConcurrentHashMap();
+
+ private ConcurrentHashMap loginFailures = new ConcurrentHashMap();
+ private final ConcurrentHashMap userSessionsByBrokerSessionId = new ConcurrentHashMap<>();
+ private final ConcurrentHashMap> userSessionsByBrokerUserId = new ConcurrentHashMap<>();
+
+ public UserSessionProvider create(KeycloakSession session) {
+ return new MemUserSessionProvider(session, userSessions, userSessionsByBrokerSessionId, userSessionsByBrokerUserId, clientSessions, loginFailures);
+ }
+
+ public void close() {
+ userSessions.clear();
+ loginFailures.clear();
+ userSessionsByBrokerSessionId.clear();
+ userSessionsByBrokerUserId.clear();
+ }
+
+}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/UserSessionAdapter.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/UserSessionAdapter.java
new file mode 100755
index 0000000000..fbe86820ad
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/UserSessionAdapter.java
@@ -0,0 +1,148 @@
+package org.keycloak.models.sessions.infinispan.compat;
+
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.sessions.infinispan.compat.entities.ClientSessionEntity;
+import org.keycloak.models.sessions.infinispan.compat.entities.UserSessionEntity;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author Stian Thorgersen
+ */
+public class UserSessionAdapter implements UserSessionModel {
+
+ private final KeycloakSession session;
+
+ private MemUserSessionProvider provider;
+ private final RealmModel realm;
+
+ private final UserSessionEntity entity;
+
+ public UserSessionAdapter(KeycloakSession session, MemUserSessionProvider provider, RealmModel realm, UserSessionEntity entity) {
+ this.session = session;
+ this.provider = provider;
+ this.realm = realm;
+ this.entity = entity;
+ }
+
+ public UserSessionEntity getEntity() {
+ return entity;
+ }
+
+ public String getId() {
+ return entity.getId();
+ }
+
+ @Override
+ public String getBrokerSessionId() {
+ return entity.getBrokerSessionId();
+ }
+
+ @Override
+ public String getBrokerUserId() {
+ return entity.getBrokerUserId();
+ }
+
+ public void setId(String id) {
+ entity.setId(id);
+ }
+
+ public UserModel getUser() {
+ return session.users().getUserById(entity.getUser(), realm);
+ }
+
+ public void setUser(UserModel user) {
+ entity.setUser(user.getId());
+ }
+
+ @Override
+ public String getLoginUsername() {
+ return entity.getLoginUsername();
+ }
+
+ public String getIpAddress() {
+ return entity.getIpAddress();
+ }
+
+ @Override
+ public String getAuthMethod() {
+ return entity.getAuthMethod();
+ }
+
+ @Override
+ public boolean isRememberMe() {
+ return entity.isRememberMe();
+ }
+
+ public int getStarted() {
+ return entity.getStarted();
+ }
+
+ public int getLastSessionRefresh() {
+ return entity.getLastSessionRefresh();
+ }
+
+ public void setLastSessionRefresh(int lastSessionRefresh) {
+ entity.setLastSessionRefresh(lastSessionRefresh);
+ }
+
+ @Override
+ public State getState() {
+ return entity.getState();
+ }
+
+ @Override
+ public void setState(State state) {
+ entity.setState(state);
+
+ }
+
+ @Override
+ public List getClientSessions() {
+ List clientSessionModels = new LinkedList();
+ if (entity.getClientSessions() != null) {
+ for (ClientSessionEntity e : entity.getClientSessions()) {
+ clientSessionModels.add(new ClientSessionAdapter(session, provider, realm, e));
+ }
+ }
+ return clientSessionModels;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || !(o instanceof UserSessionModel)) return false;
+
+ UserSessionModel that = (UserSessionModel) o;
+ return that.getId().equals(getId());
+ }
+
+ @Override
+ public int hashCode() {
+ return getId().hashCode();
+ }
+
+ @Override
+ public String getNote(String name) {
+ return entity.getNotes().get(name);
+ }
+
+ @Override
+ public void setNote(String name, String value) {
+ entity.getNotes().put(name, value);
+
+ }
+
+ @Override
+ public void removeNote(String name) {
+ entity.getNotes().remove(name);
+
+ }
+
+
+}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/UsernameLoginFailureAdapter.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/UsernameLoginFailureAdapter.java
new file mode 100644
index 0000000000..e287ebb4cd
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/UsernameLoginFailureAdapter.java
@@ -0,0 +1,71 @@
+package org.keycloak.models.sessions.infinispan.compat;
+
+import org.keycloak.models.UsernameLoginFailureModel;
+import org.keycloak.models.sessions.infinispan.compat.entities.UsernameLoginFailureEntity;
+
+/**
+ * @author Stian Thorgersen
+ */
+public class UsernameLoginFailureAdapter implements UsernameLoginFailureModel {
+
+ private final UsernameLoginFailureEntity entity;
+
+ public UsernameLoginFailureAdapter(UsernameLoginFailureEntity entity) {
+ this.entity = entity;
+ }
+
+ @Override
+ public String getUsername() {
+ return entity.getUsername();
+ }
+
+ public String getRealm() {
+ return entity.getRealm();
+ }
+
+ @Override
+ public int getFailedLoginNotBefore() {
+ return entity.getFailedLoginNotBefore().get();
+ }
+
+ @Override
+ public void setFailedLoginNotBefore(int notBefore) {
+ entity.getFailedLoginNotBefore().set(notBefore);
+ }
+
+ @Override
+ public int getNumFailures() {
+ return entity.getNumFailures().get();
+ }
+
+ @Override
+ public void incrementFailures() {
+ entity.getNumFailures().incrementAndGet();
+ }
+
+ @Override
+ public void clearFailures() {
+ entity.clearFailures();
+ }
+
+ @Override
+ public long getLastFailure() {
+ return entity.getLastFailure().get();
+ }
+
+ @Override
+ public void setLastFailure(long lastFailure) {
+ entity.getLastFailure().set(lastFailure);
+ }
+
+ @Override
+ public String getLastIPFailure() {
+ return entity.getLastIpFailure().get();
+ }
+
+ @Override
+ public void setLastIPFailure(String ip) {
+ entity.getLastIpFailure().set(ip);
+ }
+
+}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/entities/ClientSessionEntity.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/entities/ClientSessionEntity.java
new file mode 100755
index 0000000000..73336b74ab
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/entities/ClientSessionEntity.java
@@ -0,0 +1,152 @@
+package org.keycloak.models.sessions.infinispan.compat.entities;
+
+import org.keycloak.models.ClientSessionModel;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Stian Thorgersen
+ */
+public class ClientSessionEntity {
+
+ private String id;
+ private String clientId;
+ private String realmId;
+ private Map authenticatorStatus = new HashMap<>();
+ private String authUserId;
+
+ private UserSessionEntity session;
+
+ private String redirectUri;
+ private String authMethod;
+
+ private int timestamp;
+ private String action;
+ private Set roles;
+ private Set protocolMappers;
+ private Map notes = new HashMap<>();
+ private Map userSessionNotes = new HashMap<>();
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getClientId() {
+ return clientId;
+ }
+
+ public void setClientId(String clientId) {
+ this.clientId = clientId;
+ }
+
+ public String getRealmId() {
+ return realmId;
+ }
+
+ public void setRealmId(String realmId) {
+ this.realmId = realmId;
+ }
+
+ public UserSessionEntity getSession() {
+ return session;
+ }
+
+ public void setSession(UserSessionEntity session) {
+ this.session = session;
+ }
+
+ public String getRedirectUri() {
+ return redirectUri;
+ }
+
+ public void setRedirectUri(String redirectUri) {
+ this.redirectUri = redirectUri;
+ }
+
+ public int getTimestamp() {
+ return timestamp;
+ }
+
+ public void setTimestamp(int timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ public String getAction() {
+ return action;
+ }
+
+ public void setAction(String action) {
+ this.action = action;
+ }
+
+ public Set getRoles() {
+ return roles;
+ }
+
+ public void setRoles(Set roles) {
+ this.roles = roles;
+ }
+
+ public Set getProtocolMappers() {
+ return protocolMappers;
+ }
+
+ public void setProtocolMappers(Set protocolMappers) {
+ this.protocolMappers = protocolMappers;
+ }
+
+ public Map getNotes() {
+ return notes;
+ }
+
+ public String getAuthMethod() {
+ return authMethod;
+ }
+
+ public void setAuthMethod(String authMethod) {
+ this.authMethod = authMethod;
+ }
+
+ public String getAuthUserId() {
+ return authUserId;
+ }
+
+ public void setAuthUserId(String authUserId) {
+ this.authUserId = authUserId;
+ }
+
+ public Map getAuthenticatorStatus() {
+ return authenticatorStatus;
+ }
+
+ public void setAuthenticatorStatus(Map authenticatorStatus) {
+ this.authenticatorStatus = authenticatorStatus;
+ }
+
+ public Map getUserSessionNotes() {
+ return userSessionNotes;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ClientSessionEntity)) return false;
+
+ ClientSessionEntity that = (ClientSessionEntity) o;
+
+ if (id != null ? !id.equals(that.id) : that.id != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return id != null ? id.hashCode() : 0;
+ }
+}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/entities/UserSessionEntity.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/entities/UserSessionEntity.java
new file mode 100755
index 0000000000..7326aa56df
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/entities/UserSessionEntity.java
@@ -0,0 +1,147 @@
+package org.keycloak.models.sessions.infinispan.compat.entities;
+
+import org.keycloak.models.UserSessionModel;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Stian Thorgersen
+ */
+public class UserSessionEntity {
+
+ private String id;
+ private String brokerSessionId;
+ private String brokerUserId;
+ private String realm;
+ private String user;
+ private String loginUsername;
+ private String ipAddress;
+ private String authMethod;
+ private boolean rememberMe;
+ private int started;
+ private int lastSessionRefresh;
+ private UserSessionModel.State state;
+ private Map notes = new HashMap();
+ private List clientSessions = Collections.synchronizedList(new LinkedList());
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getRealm() {
+ return realm;
+ }
+
+ public void setRealm(String realm) {
+ this.realm = realm;
+ }
+
+ public String getUser() {
+ return user;
+ }
+
+ public void setUser(String user) {
+ this.user = user;
+ }
+
+ public String getLoginUsername() {
+ return loginUsername;
+ }
+
+ public void setLoginUsername(String loginUsername) {
+ this.loginUsername = loginUsername;
+ }
+
+ public String getIpAddress() {
+ return ipAddress;
+ }
+
+ public void setIpAddress(String ipAddress) {
+ this.ipAddress = ipAddress;
+ }
+
+ public String getAuthMethod() {
+ return authMethod;
+ }
+
+ public void setAuthMethod(String authMethod) {
+ this.authMethod = authMethod;
+ }
+
+ public boolean isRememberMe() {
+ return rememberMe;
+ }
+
+ public void setRememberMe(boolean rememberMe) {
+ this.rememberMe = rememberMe;
+ }
+
+ public int getStarted() {
+ return started;
+ }
+
+ public void setStarted(int started) {
+ this.started = started;
+ }
+
+ public int getLastSessionRefresh() {
+ return lastSessionRefresh;
+ }
+
+ public void setLastSessionRefresh(int lastSessionRefresh) {
+ this.lastSessionRefresh = lastSessionRefresh;
+ }
+
+ public void addClientSession(ClientSessionEntity clientSession) {
+ if (clientSessions == null) {
+ clientSessions = new LinkedList();
+ }
+ clientSessions.add(clientSession);
+ }
+
+ public void removeClientSession(ClientSessionEntity clientSession) {
+ if (clientSessions != null) {
+ clientSessions.remove(clientSession);
+ }
+ }
+
+ public List getClientSessions() {
+ return clientSessions;
+ }
+
+ public Map getNotes() {
+ return notes;
+ }
+
+ public UserSessionModel.State getState() {
+ return state;
+ }
+
+ public void setState(UserSessionModel.State state) {
+ this.state = state;
+ }
+
+ public String getBrokerSessionId() {
+ return brokerSessionId;
+ }
+
+ public void setBrokerSessionId(String brokerSessionId) {
+ this.brokerSessionId = brokerSessionId;
+ }
+
+ public String getBrokerUserId() {
+ return brokerUserId;
+ }
+
+ public void setBrokerUserId(String brokerUserId) {
+ this.brokerUserId = brokerUserId;
+ }
+}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/entities/UsernameLoginFailureEntity.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/entities/UsernameLoginFailureEntity.java
new file mode 100644
index 0000000000..f49153f132
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/entities/UsernameLoginFailureEntity.java
@@ -0,0 +1,72 @@
+package org.keycloak.models.sessions.infinispan.compat.entities;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * @author Stian Thorgersen
+ */
+public class UsernameLoginFailureEntity {
+
+ private String username;
+ private String realm;
+
+ private AtomicInteger failedLoginNotBefore = new AtomicInteger();
+ private AtomicInteger numFailures = new AtomicInteger();
+ private AtomicLong lastFailure = new AtomicLong();
+ private AtomicReference lastIpFailure = new AtomicReference();
+
+ public UsernameLoginFailureEntity(String username, String realm) {
+ this.username = username;
+ this.realm = realm;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public String getRealm() {
+ return realm;
+ }
+
+ public AtomicInteger getFailedLoginNotBefore() {
+ return failedLoginNotBefore;
+ }
+
+ public void setFailedLoginNotBefore(AtomicInteger failedLoginNotBefore) {
+ this.failedLoginNotBefore = failedLoginNotBefore;
+ }
+
+ public AtomicInteger getNumFailures() {
+ return numFailures;
+ }
+
+ public void setNumFailures(AtomicInteger numFailures) {
+ this.numFailures = numFailures;
+ }
+
+ public AtomicLong getLastFailure() {
+ return lastFailure;
+ }
+
+ public void setLastFailure(AtomicLong lastFailure) {
+ this.lastFailure = lastFailure;
+ }
+
+ public AtomicReference getLastIpFailure() {
+ return lastIpFailure;
+ }
+
+ public void setLastIpFailure(AtomicReference lastIpFailure) {
+ this.lastIpFailure = lastIpFailure;
+ }
+
+ public void clearFailures() {
+ this.failedLoginNotBefore = new AtomicInteger();
+ this.lastFailure = new AtomicLong();
+ this.lastIpFailure = new AtomicReference();
+ this.numFailures = new AtomicInteger();
+ }
+
+}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/entities/UsernameLoginFailureKey.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/entities/UsernameLoginFailureKey.java
new file mode 100644
index 0000000000..7ec3832a18
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/entities/UsernameLoginFailureKey.java
@@ -0,0 +1,36 @@
+package org.keycloak.models.sessions.infinispan.compat.entities;
+
+/**
+ * @author Stian Thorgersen
+ */
+public class UsernameLoginFailureKey {
+
+ private final String realm;
+ private final String username;
+
+ public UsernameLoginFailureKey(String realm, String username) {
+ this.realm = realm;
+ this.username = username;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ UsernameLoginFailureKey key = (UsernameLoginFailureKey) o;
+
+ if (realm != null ? !realm.equals(key.realm) : key.realm != null) return false;
+ if (username != null ? !username.equals(key.username) : key.username != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = realm != null ? realm.hashCode() : 0;
+ result = 31 * result + (username != null ? username.hashCode() : 0);
+ return result;
+ }
+
+}