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; + } + +}