diff --git a/connections/infinispan/pom.xml b/connections/infinispan/pom.xml
index 711de79ad6..3bfefdf91d 100755
--- a/connections/infinispan/pom.xml
+++ b/connections/infinispan/pom.xml
@@ -27,5 +27,6 @@
infinispan-core
provided
+
diff --git a/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java b/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java
index 58962dc5c3..bc2635c418 100755
--- a/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java
+++ b/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java
@@ -66,6 +66,19 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
} else {
initEmbedded();
}
+
+ // Backwards compatibility
+ if (cacheManager.getCacheConfiguration(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME) == null) {
+ logger.warnf("No configuration provided for '%s' cache. Using '%s' configuration as template",
+ InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, InfinispanConnectionProvider.SESSION_CACHE_NAME);
+
+ Configuration sessionCacheConfig = cacheManager.getCacheConfiguration(InfinispanConnectionProvider.SESSION_CACHE_NAME);
+ if (sessionCacheConfig != null) {
+ ConfigurationBuilder confBuilder = new ConfigurationBuilder().read(sessionCacheConfig);
+ Configuration offlineSessionConfig = confBuilder.build();
+ cacheManager.defineConfiguration(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, offlineSessionConfig);
+ }
+ }
}
}
}
diff --git a/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java b/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java
index 05cfdb9e1d..945e0de47c 100644
--- a/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java
+++ b/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java
@@ -11,6 +11,7 @@ public interface InfinispanConnectionProvider extends Provider {
static final String REALM_CACHE_NAME = "realms";
static final String USER_CACHE_NAME = "users";
static final String SESSION_CACHE_NAME = "sessions";
+ static final String OFFLINE_SESSION_CACHE_NAME = "offlineSessions";
static final String LOGIN_FAILURE_CACHE_NAME = "loginFailures";
Cache getCache(String name);
diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.6.0.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.6.0.xml
index 9d6e545b5a..a782f76f79 100644
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.6.0.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.6.0.xml
@@ -13,19 +13,23 @@
+
+
+
-
+
+
+
+
+
-
-
-
@@ -35,14 +39,13 @@
+
+
+
-
-
-
-
-
-
+
+
\ No newline at end of file
diff --git a/connections/jpa/src/main/resources/META-INF/persistence.xml b/connections/jpa/src/main/resources/META-INF/persistence.xml
index f8f33be9ad..b8592a365c 100755
--- a/connections/jpa/src/main/resources/META-INF/persistence.xml
+++ b/connections/jpa/src/main/resources/META-INF/persistence.xml
@@ -29,8 +29,8 @@
org.keycloak.models.jpa.entities.AuthenticationExecutionEntity
org.keycloak.models.jpa.entities.AuthenticatorConfigEntity
org.keycloak.models.jpa.entities.RequiredActionProviderEntity
- org.keycloak.models.jpa.entities.OfflineUserSessionEntity
- org.keycloak.models.jpa.entities.OfflineClientSessionEntity
+ org.keycloak.models.jpa.session.PersistentUserSessionEntity
+ org.keycloak.models.jpa.session.PersistentClientSessionEntity
org.keycloak.events.jpa.EventEntity
diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java
index bd5a2a295e..82f3d57b5a 100755
--- a/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java
+++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java
@@ -36,6 +36,8 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
"org.keycloak.models.mongo.keycloak.entities.MongoClientEntity",
"org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity",
"org.keycloak.models.mongo.keycloak.entities.MongoMigrationModelEntity",
+ "org.keycloak.models.mongo.keycloak.entities.MongoOnlineUserSessionEntity",
+ "org.keycloak.models.mongo.keycloak.entities.MongoOfflineUserSessionEntity",
"org.keycloak.models.entities.IdentityProviderEntity",
"org.keycloak.models.entities.ClientIdentityProviderMappingEntity",
"org.keycloak.models.entities.RequiredCredentialEntity",
@@ -49,8 +51,8 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
"org.keycloak.models.entities.AuthenticationFlowEntity",
"org.keycloak.models.entities.AuthenticatorConfigEntity",
"org.keycloak.models.entities.RequiredActionProviderEntity",
- "org.keycloak.models.entities.OfflineUserSessionEntity",
- "org.keycloak.models.entities.OfflineClientSessionEntity",
+ "org.keycloak.models.entities.PersistentUserSessionEntity",
+ "org.keycloak.models.entities.PersistentClientSessionEntity",
};
private static final Logger logger = Logger.getLogger(DefaultMongoConnectionFactoryProvider.class);
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json b/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json
index 188a2a2d3e..669cf41db5 100644
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json
@@ -22,6 +22,10 @@
"provider": "jpa"
},
+ "userSessionPersister": {
+ "provider": "jpa"
+ },
+
"timer": {
"provider": "basic"
},
diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
index 88471d0b35..9963ea13d7 100755
--- a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
+++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
@@ -1,7 +1,7 @@
package org.keycloak.exportimport.util;
-import org.keycloak.models.OfflineClientSessionModel;
-import org.keycloak.models.OfflineUserSessionModel;
+import org.keycloak.models.session.PersistentClientSessionModel;
+import org.keycloak.models.session.PersistentUserSessionModel;
import org.keycloak.representations.idm.OfflineUserSessionRepresentation;
import org.keycloak.util.Base64;
import org.codehaus.jackson.JsonEncoding;
@@ -11,7 +11,6 @@ import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
@@ -298,27 +297,27 @@ public class ExportUtils {
}
}
- // Offline sessions
- List offlineSessionReps = new LinkedList<>();
- Collection offlineSessions = session.users().getOfflineUserSessions(realm, user);
- Collection offlineClientSessions = session.users().getOfflineClientSessions(realm, user);
-
- Map> processed = new HashMap<>();
- for (OfflineClientSessionModel clsm : offlineClientSessions) {
- String userSessionId = clsm.getUserSessionId();
- List current = processed.get(userSessionId);
- if (current == null) {
- current = new LinkedList<>();
- processed.put(userSessionId, current);
- }
- current.add(clsm);
- }
-
- for (OfflineUserSessionModel userSession : offlineSessions) {
- OfflineUserSessionRepresentation sessionRep = ModelToRepresentation.toRepresentation(realm, userSession, processed.get(userSession.getUserSessionId()));
- offlineSessionReps.add(sessionRep);
- }
- userRep.setOfflineUserSessions(offlineSessionReps);
+// // Offline sessions
+// List offlineSessionReps = new LinkedList<>();
+// Collection offlineSessions = session.users().getOfflineUserSessions(realm, user);
+// Collection offlineClientSessions = session.users().getOfflineClientSessions(realm, user);
+//
+// Map> processed = new HashMap<>();
+// for (PersistentClientSessionModel clsm : offlineClientSessions) {
+// String userSessionId = clsm.getUserSessionId();
+// List current = processed.get(userSessionId);
+// if (current == null) {
+// current = new LinkedList<>();
+// processed.put(userSessionId, current);
+// }
+// current.add(clsm);
+// }
+//
+// for (PersistentUserSessionModel userSession : offlineSessions) {
+// OfflineUserSessionRepresentation sessionRep = ModelToRepresentation.toRepresentation(realm, userSession, processed.get(userSession.getUserSessionId()));
+// offlineSessionReps.add(sessionRep);
+// }
+// userRep.setOfflineUserSessions(offlineSessionReps);
return userRep;
}
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/ApplicationsBean.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/ApplicationsBean.java
index 71bf2005d8..95ebe4fe44 100644
--- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/ApplicationsBean.java
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/ApplicationsBean.java
@@ -13,7 +13,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.protocol.oidc.TokenManager;
-import org.keycloak.services.offline.OfflineTokenUtils;
+import org.keycloak.services.managers.UserSessionManager;
import org.keycloak.util.MultivaluedHashMap;
/**
@@ -25,7 +25,7 @@ public class ApplicationsBean {
public ApplicationsBean(KeycloakSession session, RealmModel realm, UserModel user) {
- Set offlineClients = OfflineTokenUtils.findClientsWithOfflineToken(session, realm, user);
+ Set offlineClients = new UserSessionManager(session).findClientsWithOfflineToken(realm, user);
List realmClients = realm.getClients();
for (ClientModel client : realmClients) {
diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index 13d111d63c..452e2b35d9 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -97,6 +97,9 @@ public interface RealmModel extends RoleContainerModel {
int getSsoSessionMaxLifespan();
void setSsoSessionMaxLifespan(int seconds);
+// int getOfflineSessionIdleTimeout();
+// void setOfflineSessionIdleTimeout(int seconds);
+
int getAccessTokenLifespan();
void setAccessTokenLifespan(int seconds);
@@ -286,6 +289,10 @@ public interface RealmModel extends RoleContainerModel {
void setEventsEnabled(boolean enabled);
+// boolean isPersistUserSessions();
+//
+// void setPersistUserSessions();
+
long getEventsExpiration();
void setEventsExpiration(long expiration);
diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
index b5020844f3..bea51e0473 100755
--- a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
@@ -475,70 +475,6 @@ public class UserFederationManager implements UserProvider {
return (result != null) ? result : CredentialValidationOutput.failed();
}
- @Override
- public void addOfflineUserSession(RealmModel realm, UserModel user, OfflineUserSessionModel offlineUserSession) {
- validateUser(realm, user);
- if (user == null) throw new IllegalStateException("Federated user no longer valid");
- session.userStorage().addOfflineUserSession(realm, user, offlineUserSession);
- }
-
- @Override
- public OfflineUserSessionModel getOfflineUserSession(RealmModel realm, UserModel user, String userSessionId) {
- validateUser(realm, user);
- if (user == null) throw new IllegalStateException("Federated user no longer valid");
- return session.userStorage().getOfflineUserSession(realm, user, userSessionId);
- }
-
- @Override
- public Collection getOfflineUserSessions(RealmModel realm, UserModel user) {
- validateUser(realm, user);
- if (user == null) throw new IllegalStateException("Federated user no longer valid");
- return session.userStorage().getOfflineUserSessions(realm, user);
- }
-
- @Override
- public boolean removeOfflineUserSession(RealmModel realm, UserModel user, String userSessionId) {
- validateUser(realm, user);
- if (user == null) throw new IllegalStateException("Federated user no longer valid");
- return session.userStorage().removeOfflineUserSession(realm, user, userSessionId);
- }
-
- @Override
- public void addOfflineClientSession(RealmModel realm, OfflineClientSessionModel offlineClientSession) {
- session.userStorage().addOfflineClientSession(realm, offlineClientSession);
- }
-
- @Override
- public OfflineClientSessionModel getOfflineClientSession(RealmModel realm, UserModel user, String clientSessionId) {
- validateUser(realm, user);
- if (user == null) throw new IllegalStateException("Federated user no longer valid");
- return session.userStorage().getOfflineClientSession(realm, user, clientSessionId);
- }
-
- @Override
- public Collection getOfflineClientSessions(RealmModel realm, UserModel user) {
- validateUser(realm, user);
- if (user == null) throw new IllegalStateException("Federated user no longer valid");
- return session.userStorage().getOfflineClientSessions(realm, user);
- }
-
- @Override
- public boolean removeOfflineClientSession(RealmModel realm, UserModel user, String clientSessionId) {
- validateUser(realm, user);
- if (user == null) throw new IllegalStateException("Federated user no longer valid");
- return session.userStorage().removeOfflineClientSession(realm, user, clientSessionId);
- }
-
- @Override
- public int getOfflineClientSessionsCount(RealmModel realm, ClientModel client) {
- return session.userStorage().getOfflineClientSessionsCount(realm, client);
- }
-
- @Override
- public Collection getOfflineClientSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
- return session.userStorage().getOfflineClientSessions(realm, client, firstResult, maxResults);
- }
-
@Override
public void close() {
}
diff --git a/model/api/src/main/java/org/keycloak/models/UserProvider.java b/model/api/src/main/java/org/keycloak/models/UserProvider.java
index 0012b24b0b..7d7064d76e 100755
--- a/model/api/src/main/java/org/keycloak/models/UserProvider.java
+++ b/model/api/src/main/java/org/keycloak/models/UserProvider.java
@@ -56,17 +56,5 @@ public interface UserProvider extends Provider {
boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input);
CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel... input);
- void addOfflineUserSession(RealmModel realm, UserModel user, OfflineUserSessionModel offlineUserSession);
- OfflineUserSessionModel getOfflineUserSession(RealmModel realm, UserModel user, String userSessionId);
- Collection getOfflineUserSessions(RealmModel realm, UserModel user);
- boolean removeOfflineUserSession(RealmModel realm, UserModel user, String userSessionId);
- void addOfflineClientSession(RealmModel realm, OfflineClientSessionModel offlineClientSession);
- OfflineClientSessionModel getOfflineClientSession(RealmModel realm, UserModel user, String clientSessionId);
- Collection getOfflineClientSessions(RealmModel realm, UserModel user);
- boolean removeOfflineClientSession(RealmModel realm, UserModel user, String clientSessionId);
-
- int getOfflineClientSessionsCount(RealmModel realm, ClientModel client);
- Collection getOfflineClientSessions(RealmModel realm, ClientModel client, int first, int max);
-
void close();
}
diff --git a/model/api/src/main/java/org/keycloak/models/UserSessionModel.java b/model/api/src/main/java/org/keycloak/models/UserSessionModel.java
index 12ebd70c2f..8db5cdcffc 100755
--- a/model/api/src/main/java/org/keycloak/models/UserSessionModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserSessionModel.java
@@ -9,6 +9,7 @@ import java.util.Map;
public interface UserSessionModel {
String getId();
+ RealmModel getRealm();
/**
* If created via a broker external login, this is an identifier that can be
diff --git a/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java b/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java
index 3b4d1472d7..836cc75769 100755
--- a/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java
+++ b/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java
@@ -2,6 +2,7 @@ package org.keycloak.models;
import org.keycloak.provider.Provider;
+import java.util.Collection;
import java.util.List;
/**
@@ -27,6 +28,8 @@ public interface UserSessionProvider extends Provider {
int getActiveUserSessions(RealmModel realm, ClientModel client);
void removeUserSession(RealmModel realm, UserSessionModel session);
void removeUserSessions(RealmModel realm, UserModel user);
+
+ // Implementation should propagate removal of expired userSessions to userSessionPersister too
void removeExpiredUserSessions(RealmModel realm);
void removeUserSessions(RealmModel realm);
void removeClientSession(RealmModel realm, ClientSessionModel clientSession);
@@ -40,6 +43,22 @@ public interface UserSessionProvider extends Provider {
void onClientRemoved(RealmModel realm, ClientModel client);
void onUserRemoved(RealmModel realm, UserModel user);
+ UserSessionModel createOfflineUserSession(UserSessionModel userSession);
+ UserSessionModel getOfflineUserSession(RealmModel realm, String userSessionId);
+
+ // Removes the attached clientSessions as well
+ void removeOfflineUserSession(RealmModel realm, String userSessionId);
+
+ ClientSessionModel createOfflineClientSession(ClientSessionModel clientSession);
+ ClientSessionModel getOfflineClientSession(RealmModel realm, String clientSessionId);
+ List getOfflineClientSessions(RealmModel realm, UserModel user);
+
+ // Don't remove userSession even if it's last userSession
+ void removeOfflineClientSession(RealmModel realm, String clientSessionId);
+
+ int getOfflineSessionsCount(RealmModel realm, ClientModel client);
+ List getOfflineUserSessions(RealmModel realm, ClientModel client, int first, int max);
+
void close();
}
diff --git a/model/api/src/main/java/org/keycloak/models/UserSessionProviderFactory.java b/model/api/src/main/java/org/keycloak/models/UserSessionProviderFactory.java
index b57b55e31d..93b6f322a4 100755
--- a/model/api/src/main/java/org/keycloak/models/UserSessionProviderFactory.java
+++ b/model/api/src/main/java/org/keycloak/models/UserSessionProviderFactory.java
@@ -7,4 +7,8 @@ import org.keycloak.provider.ProviderFactory;
* @version $Revision: 1 $
*/
public interface UserSessionProviderFactory extends ProviderFactory {
+
+ // This is supposed to prefill all userSessions and clientSessions from userSessionPersister to the userSession infinispan/memory storage
+ void loadPersistentSessions(KeycloakSessionFactory sessionFactory, final int maxErrors, final int sessionsPerSegment);
+
}
diff --git a/model/api/src/main/java/org/keycloak/models/entities/OfflineUserSessionEntity.java b/model/api/src/main/java/org/keycloak/models/entities/OfflineUserSessionEntity.java
deleted file mode 100644
index e7858983fb..0000000000
--- a/model/api/src/main/java/org/keycloak/models/entities/OfflineUserSessionEntity.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package org.keycloak.models.entities;
-
-import java.util.List;
-
-/**
- * @author Marek Posolda
- */
-public class OfflineUserSessionEntity {
-
- private String userSessionId;
- private String data;
- private List offlineClientSessions;
-
- public String getUserSessionId() {
- return userSessionId;
- }
-
- public void setUserSessionId(String userSessionId) {
- this.userSessionId = userSessionId;
- }
-
- public String getData() {
- return data;
- }
-
- public void setData(String data) {
- this.data = data;
- }
-
- public List getOfflineClientSessions() {
- return offlineClientSessions;
- }
-
- public void setOfflineClientSessions(List offlineClientSessions) {
- this.offlineClientSessions = offlineClientSessions;
- }
-}
diff --git a/model/api/src/main/java/org/keycloak/models/entities/OfflineClientSessionEntity.java b/model/api/src/main/java/org/keycloak/models/entities/PersistentClientSessionEntity.java
similarity index 93%
rename from model/api/src/main/java/org/keycloak/models/entities/OfflineClientSessionEntity.java
rename to model/api/src/main/java/org/keycloak/models/entities/PersistentClientSessionEntity.java
index 69ad60bb38..a03edd31d5 100644
--- a/model/api/src/main/java/org/keycloak/models/entities/OfflineClientSessionEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/PersistentClientSessionEntity.java
@@ -3,7 +3,7 @@ package org.keycloak.models.entities;
/**
* @author Marek Posolda
*/
-public class OfflineClientSessionEntity {
+public class PersistentClientSessionEntity {
private String clientSessionId;
private String clientId;
diff --git a/model/api/src/main/java/org/keycloak/models/entities/PersistentUserSessionEntity.java b/model/api/src/main/java/org/keycloak/models/entities/PersistentUserSessionEntity.java
new file mode 100644
index 0000000000..dd5262cbf4
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/entities/PersistentUserSessionEntity.java
@@ -0,0 +1,64 @@
+package org.keycloak.models.entities;
+
+import java.util.List;
+
+/**
+ * @author Marek Posolda
+ */
+public class PersistentUserSessionEntity {
+
+ private String id;
+ private String realmId;
+ private String userId;
+ private int lastSessionRefresh;
+ private String data;
+ private List clientSessions;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getRealmId() {
+ return realmId;
+ }
+
+ public void setRealmId(String realmId) {
+ this.realmId = realmId;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public void setUserId(String userId) {
+ this.userId = userId;
+ }
+
+ public int getLastSessionRefresh() {
+ return lastSessionRefresh;
+ }
+
+ public void setLastSessionRefresh(int lastSessionRefresh) {
+ this.lastSessionRefresh = lastSessionRefresh;
+ }
+
+ public String getData() {
+ return data;
+ }
+
+ public void setData(String data) {
+ this.data = data;
+ }
+
+ public List getClientSessions() {
+ return clientSessions;
+ }
+
+ public void setClientSessions(List clientSessions) {
+ this.clientSessions = clientSessions;
+ }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/entities/UserEntity.java b/model/api/src/main/java/org/keycloak/models/entities/UserEntity.java
index 66020db921..8c82a8e13b 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/UserEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/UserEntity.java
@@ -28,7 +28,6 @@ public class UserEntity extends AbstractIdentifiableEntity {
private List federatedIdentities;
private String federationLink;
private String serviceAccountClientLink;
- private List offlineUserSessions;
public String getUsername() {
return username;
@@ -158,13 +157,5 @@ public class UserEntity extends AbstractIdentifiableEntity {
public void setServiceAccountClientLink(String serviceAccountClientLink) {
this.serviceAccountClientLink = serviceAccountClientLink;
}
-
- public List getOfflineUserSessions() {
- return offlineUserSessions;
- }
-
- public void setOfflineUserSessions(List offlineUserSessions) {
- this.offlineUserSessions = offlineUserSessions;
- }
}
diff --git a/model/api/src/main/java/org/keycloak/models/session/DisabledUserSessionPersisterProvider.java b/model/api/src/main/java/org/keycloak/models/session/DisabledUserSessionPersisterProvider.java
new file mode 100644
index 0000000000..6daf7c749c
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/session/DisabledUserSessionPersisterProvider.java
@@ -0,0 +1,104 @@
+package org.keycloak.models.session;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.keycloak.Config;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+
+/**
+ * Persistence of userSessions is disabled . Useful just if you never need survive of userSessions/clientSessions
+ * among server restart. Offline sessions / offline tokens will be invalid after server restart as well,
+ *
+ * @author Marek Posolda
+ */
+public class DisabledUserSessionPersisterProvider implements UserSessionPersisterProviderFactory, UserSessionPersisterProvider {
+
+ public static final String ID = "disabled";
+
+ @Override
+ public UserSessionPersisterProvider create(KeycloakSession session) {
+ return this;
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public String getId() {
+ return ID;
+ }
+
+ @Override
+ public void createUserSession(UserSessionModel userSession, boolean offline) {
+
+ }
+
+ @Override
+ public void createClientSession(ClientSessionModel clientSession, boolean offline) {
+
+ }
+
+ @Override
+ public void updateUserSession(UserSessionModel userSession, boolean offline) {
+
+ }
+
+ @Override
+ public void removeUserSession(String userSessionId, boolean offline) {
+
+ }
+
+ @Override
+ public void removeClientSession(String clientSessionId, boolean offline) {
+
+ }
+
+ @Override
+ public void onRealmRemoved(RealmModel realm) {
+
+ }
+
+ @Override
+ public void onClientRemoved(RealmModel realm, ClientModel client) {
+
+ }
+
+ @Override
+ public void onUserRemoved(RealmModel realm, UserModel user) {
+
+ }
+
+ @Override
+ public void clearDetachedUserSessions() {
+
+ }
+
+ @Override
+ public List loadUserSessions(int firstResult, int maxResults, boolean offline) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public int getUserSessionsCount(boolean offline) {
+ return 0;
+ }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/session/PersistentClientSessionAdapter.java b/model/api/src/main/java/org/keycloak/models/session/PersistentClientSessionAdapter.java
new file mode 100644
index 0000000000..1fced88bbe
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/session/PersistentClientSessionAdapter.java
@@ -0,0 +1,401 @@
+package org.keycloak.models.session;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * @author Marek Posolda
+ */
+public class PersistentClientSessionAdapter implements ClientSessionModel {
+
+ private final PersistentClientSessionModel model;
+ private final RealmModel realm;
+ private final ClientModel client;
+ private UserSessionModel userSession;
+
+ private PersistentClientSessionData data;
+
+ public PersistentClientSessionAdapter(ClientSessionModel clientSession) {
+ data = new PersistentClientSessionData();
+ data.setAction(clientSession.getAction());
+ data.setAuthMethod(clientSession.getAuthMethod());
+ data.setExecutionStatus(clientSession.getExecutionStatus());
+ data.setNotes(clientSession.getNotes());
+ data.setProtocolMappers(clientSession.getProtocolMappers());
+ data.setRedirectUri(clientSession.getRedirectUri());
+ data.setRoles(clientSession.getRoles());
+ data.setTimestamp(clientSession.getTimestamp());
+ data.setUserSessionNotes(clientSession.getUserSessionNotes());
+
+ model = new PersistentClientSessionModel();
+ model.setClientId(clientSession.getClient().getId());
+ model.setClientSessionId(clientSession.getId());
+ if (clientSession.getAuthenticatedUser() != null) {
+ model.setUserId(clientSession.getAuthenticatedUser().getId());
+ }
+ model.setUserSessionId(clientSession.getUserSession().getId());
+
+ realm = clientSession.getRealm();
+ client = clientSession.getClient();
+ userSession = clientSession.getUserSession();
+ }
+
+ public PersistentClientSessionAdapter(PersistentClientSessionModel model, RealmModel realm, ClientModel client, UserSessionModel userSession) {
+ this.model = model;
+ this.realm = realm;
+ this.client = client;
+ this.userSession = userSession;
+ }
+
+ // Lazily init data
+ private PersistentClientSessionData getData() {
+ if (data == null) {
+ try {
+ data = JsonSerialization.readValue(model.getData(), PersistentClientSessionData.class);
+ } catch (IOException ioe) {
+ throw new ModelException(ioe);
+ }
+ }
+
+ return data;
+ }
+
+ // Write updated model with latest serialized data
+ public PersistentClientSessionModel getUpdatedModel() {
+ try {
+ String updatedData = JsonSerialization.writeValueAsString(getData());
+ this.model.setData(updatedData);
+ } catch (IOException ioe) {
+ throw new ModelException(ioe);
+ }
+
+ return this.model;
+ }
+
+ @Override
+ public String getId() {
+ return model.getClientSessionId();
+ }
+
+ @Override
+ public RealmModel getRealm() {
+ return realm;
+ }
+
+ @Override
+ public ClientModel getClient() {
+ return client;
+ }
+
+ @Override
+ public UserSessionModel getUserSession() {
+ return userSession;
+ }
+
+ @Override
+ public void setUserSession(UserSessionModel userSession) {
+ this.userSession = userSession;
+ }
+
+ @Override
+ public String getRedirectUri() {
+ return getData().getRedirectUri();
+ }
+
+ @Override
+ public void setRedirectUri(String uri) {
+ getData().setRedirectUri(uri);
+ }
+
+ @Override
+ public int getTimestamp() {
+ return getData().getTimestamp();
+ }
+
+ @Override
+ public void setTimestamp(int timestamp) {
+ getData().setTimestamp(timestamp);
+ }
+
+ @Override
+ public String getAction() {
+ return getData().getAction();
+ }
+
+ @Override
+ public void setAction(String action) {
+ getData().setAction(action);
+ }
+
+ @Override
+ public Set getRoles() {
+ return getData().getRoles();
+ }
+
+ @Override
+ public void setRoles(Set roles) {
+ getData().setRoles(roles);
+ }
+
+ @Override
+ public Set getProtocolMappers() {
+ return getData().getProtocolMappers();
+ }
+
+ @Override
+ public void setProtocolMappers(Set protocolMappers) {
+ getData().setProtocolMappers(protocolMappers);
+ }
+
+ @Override
+ public Map getExecutionStatus() {
+ return getData().getExecutionStatus();
+ }
+
+ @Override
+ public void setExecutionStatus(String authenticator, ExecutionStatus status) {
+ getData().getExecutionStatus().put(authenticator, status);
+ }
+
+ @Override
+ public void clearExecutionStatus() {
+ getData().getExecutionStatus().clear();
+ }
+
+ @Override
+ public UserModel getAuthenticatedUser() {
+ return userSession.getUser();
+ }
+
+ @Override
+ public void setAuthenticatedUser(UserModel user) {
+ throw new IllegalStateException("Not supported setAuthenticatedUser");
+ }
+
+ @Override
+ public String getAuthMethod() {
+ return getData().getAuthMethod();
+ }
+
+ @Override
+ public void setAuthMethod(String method) {
+ getData().setAuthMethod(method);
+ }
+
+ @Override
+ public String getNote(String name) {
+ PersistentClientSessionData entity = getData();
+ return entity.getNotes()==null ? null : entity.getNotes().get(name);
+ }
+
+ @Override
+ public void setNote(String name, String value) {
+ PersistentClientSessionData entity = getData();
+ if (entity.getNotes() == null) {
+ entity.setNotes(new HashMap());
+ }
+ entity.getNotes().put(name, value);
+ }
+
+ @Override
+ public void removeNote(String name) {
+ PersistentClientSessionData entity = getData();
+ if (entity.getNotes() != null) {
+ entity.getNotes().remove(name);
+ }
+ }
+
+ @Override
+ public Map getNotes() {
+ PersistentClientSessionData entity = getData();
+ if (entity.getNotes() == null || entity.getNotes().isEmpty()) return Collections.emptyMap();
+ return entity.getNotes();
+ }
+
+ @Override
+ public Set getRequiredActions() {
+ return getData().getRequiredActions();
+ }
+
+ @Override
+ public void addRequiredAction(String action) {
+ getData().getRequiredActions().add(action);
+ }
+
+ @Override
+ public void removeRequiredAction(String action) {
+ getData().getRequiredActions().remove(action);
+ }
+
+ @Override
+ public void addRequiredAction(UserModel.RequiredAction action) {
+ addRequiredAction(action.name());
+ }
+
+ @Override
+ public void removeRequiredAction(UserModel.RequiredAction action) {
+ removeRequiredAction(action.name());
+ }
+
+ @Override
+ public void setUserSessionNote(String name, String value) {
+ PersistentClientSessionData entity = getData();
+ if (entity.getUserSessionNotes() == null) {
+ entity.setUserSessionNotes(new HashMap());
+ }
+ entity.getUserSessionNotes().put(name, value);
+ }
+
+ @Override
+ public Map getUserSessionNotes() {
+ PersistentClientSessionData entity = getData();
+ if (entity.getUserSessionNotes() == null || entity.getUserSessionNotes().isEmpty()) return Collections.emptyMap();
+ return entity.getUserSessionNotes();
+ }
+
+ @Override
+ public void clearUserSessionNotes() {
+ PersistentClientSessionData entity = getData();
+ entity.setUserSessionNotes(new HashMap());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || !(o instanceof ClientSessionModel)) return false;
+
+ ClientSessionModel that = (ClientSessionModel) o;
+ return that.getId().equals(getId());
+ }
+
+ @Override
+ public int hashCode() {
+ return getId().hashCode();
+ }
+
+ protected static class PersistentClientSessionData {
+
+ @JsonProperty("authMethod")
+ private String authMethod;
+
+ @JsonProperty("redirectUri")
+ private String redirectUri;
+
+ @JsonProperty("protocolMappers")
+ private Set protocolMappers;
+
+ @JsonProperty("roles")
+ private Set roles;
+
+ @JsonProperty("notes")
+ private Map notes;
+
+ @JsonProperty("userSessionNotes")
+ private Map userSessionNotes;
+
+ @JsonProperty("executionStatus")
+ private Map executionStatus = new HashMap<>();
+
+ @JsonProperty("timestamp")
+ private int timestamp;
+
+ @JsonProperty("action")
+ private String action;
+
+ @JsonProperty("requiredActions")
+ private Set requiredActions = new HashSet<>();
+
+ public String getAuthMethod() {
+ return authMethod;
+ }
+
+ public void setAuthMethod(String authMethod) {
+ this.authMethod = authMethod;
+ }
+
+ public String getRedirectUri() {
+ return redirectUri;
+ }
+
+ public void setRedirectUri(String redirectUri) {
+ this.redirectUri = redirectUri;
+ }
+
+ public Set getProtocolMappers() {
+ return protocolMappers;
+ }
+
+ public void setProtocolMappers(Set protocolMappers) {
+ this.protocolMappers = protocolMappers;
+ }
+
+ public Set getRoles() {
+ return roles;
+ }
+
+ public void setRoles(Set roles) {
+ this.roles = roles;
+ }
+
+ public Map getNotes() {
+ return notes;
+ }
+
+ public void setNotes(Map notes) {
+ this.notes = notes;
+ }
+
+ public Map getUserSessionNotes() {
+ return userSessionNotes;
+ }
+
+ public void setUserSessionNotes(Map userSessionNotes) {
+ this.userSessionNotes = userSessionNotes;
+ }
+
+ public Map getExecutionStatus() {
+ return executionStatus;
+ }
+
+ public void setExecutionStatus(Map executionStatus) {
+ this.executionStatus = executionStatus;
+ }
+
+ 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 getRequiredActions() {
+ return requiredActions;
+ }
+
+ public void setRequiredActions(Set requiredActions) {
+ this.requiredActions = requiredActions;
+ }
+ }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/OfflineClientSessionModel.java b/model/api/src/main/java/org/keycloak/models/session/PersistentClientSessionModel.java
similarity index 92%
rename from model/api/src/main/java/org/keycloak/models/OfflineClientSessionModel.java
rename to model/api/src/main/java/org/keycloak/models/session/PersistentClientSessionModel.java
index 59e325d8d2..96e900fb7b 100644
--- a/model/api/src/main/java/org/keycloak/models/OfflineClientSessionModel.java
+++ b/model/api/src/main/java/org/keycloak/models/session/PersistentClientSessionModel.java
@@ -1,9 +1,9 @@
-package org.keycloak.models;
+package org.keycloak.models.session;
/**
* @author Marek Posolda
*/
-public class OfflineClientSessionModel {
+public class PersistentClientSessionModel {
private String clientSessionId;
private String userSessionId;
diff --git a/services/src/main/java/org/keycloak/services/offline/OfflineUserSessionAdapter.java b/model/api/src/main/java/org/keycloak/models/session/PersistentUserSessionAdapter.java
similarity index 57%
rename from services/src/main/java/org/keycloak/services/offline/OfflineUserSessionAdapter.java
rename to model/api/src/main/java/org/keycloak/models/session/PersistentUserSessionAdapter.java
index bd01d3884e..e5fc68c4aa 100644
--- a/services/src/main/java/org/keycloak/services/offline/OfflineUserSessionAdapter.java
+++ b/model/api/src/main/java/org/keycloak/models/session/PersistentUserSessionAdapter.java
@@ -1,13 +1,14 @@
-package org.keycloak.services.offline;
+package org.keycloak.models.session;
import java.io.IOException;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.codehaus.jackson.annotate.JsonProperty;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.ModelException;
-import org.keycloak.models.OfflineUserSessionModel;
+import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.util.JsonSerialization;
@@ -15,23 +16,47 @@ import org.keycloak.util.JsonSerialization;
/**
* @author Marek Posolda
*/
-public class OfflineUserSessionAdapter implements UserSessionModel {
+public class PersistentUserSessionAdapter implements UserSessionModel {
- private final OfflineUserSessionModel model;
+ private final PersistentUserSessionModel model;
private final UserModel user;
+ private final RealmModel realm;
+ private final List clientSessions;
- private OfflineUserSessionData data;
+ private PersistentUserSessionData data;
- public OfflineUserSessionAdapter(OfflineUserSessionModel model, UserModel user) {
- this.model = model;
- this.user = user;
+ public PersistentUserSessionAdapter(UserSessionModel other) {
+ this.data = new PersistentUserSessionData();
+ data.setAuthMethod(other.getAuthMethod());
+ data.setBrokerSessionId(other.getBrokerSessionId());
+ data.setBrokerUserId(other.getBrokerUserId());
+ data.setIpAddress(other.getIpAddress());
+ data.setNotes(other.getNotes());
+ data.setRememberMe(other.isRememberMe());
+ data.setStarted(other.getStarted());
+ data.setState(other.getState());
+
+ this.model = new PersistentUserSessionModel();
+ this.model.setUserSessionId(other.getId());
+ this.model.setLastSessionRefresh(other.getLastSessionRefresh());
+
+ this.user = other.getUser();
+ this.realm = other.getRealm();
+ this.clientSessions = other.getClientSessions();
}
- // lazily init representation
- private OfflineUserSessionData getData() {
+ public PersistentUserSessionAdapter(PersistentUserSessionModel model, RealmModel realm, UserModel user, List clientSessions) {
+ this.model = model;
+ this.realm = realm;
+ this.user = user;
+ this.clientSessions = clientSessions;
+ }
+
+ // Lazily init data
+ private PersistentUserSessionData getData() {
if (data == null) {
try {
- data = JsonSerialization.readValue(model.getData(), OfflineUserSessionData.class);
+ data = JsonSerialization.readValue(model.getData(), PersistentUserSessionData.class);
} catch (IOException ioe) {
throw new ModelException(ioe);
}
@@ -40,6 +65,18 @@ public class OfflineUserSessionAdapter implements UserSessionModel {
return data;
}
+ // Write updated model with latest serialized data
+ public PersistentUserSessionModel getUpdatedModel() {
+ try {
+ String updatedData = JsonSerialization.writeValueAsString(getData());
+ this.model.setData(updatedData);
+ } catch (IOException ioe) {
+ throw new ModelException(ioe);
+ }
+
+ return this.model;
+ }
+
@Override
public String getId() {
return model.getUserSessionId();
@@ -60,6 +97,11 @@ public class OfflineUserSessionAdapter implements UserSessionModel {
return user;
}
+ @Override
+ public RealmModel getRealm() {
+ return realm;
+ }
+
@Override
public String getLoginUsername() {
return user.getUsername();
@@ -87,17 +129,17 @@ public class OfflineUserSessionAdapter implements UserSessionModel {
@Override
public int getLastSessionRefresh() {
- return 0;
+ return model.getLastSessionRefresh();
}
@Override
public void setLastSessionRefresh(int seconds) {
- // Ignore
+ model.setLastSessionRefresh(seconds);
}
@Override
public List getClientSessions() {
- throw new IllegalStateException("Not yet supported");
+ return clientSessions;
}
@Override
@@ -107,13 +149,19 @@ public class OfflineUserSessionAdapter implements UserSessionModel {
@Override
public void setNote(String name, String value) {
- throw new IllegalStateException("Illegal to set note offline session");
+ PersistentUserSessionData data = getData();
+ if (data.getNotes() == null) {
+ data.setNotes(new HashMap());
+ }
+ data.getNotes().put(name, value);
}
@Override
public void removeNote(String name) {
- throw new IllegalStateException("Illegal to remove note from offline session");
+ if (getData().getNotes() != null) {
+ getData().getNotes().remove(name);
+ }
}
@Override
@@ -123,16 +171,29 @@ public class OfflineUserSessionAdapter implements UserSessionModel {
@Override
public State getState() {
- return null;
+ return getData().getState();
}
@Override
public void setState(State state) {
- throw new IllegalStateException("Illegal to set state on offline session");
+ getData().setState(state);
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || !(o instanceof UserSessionModel)) return false;
- protected static class OfflineUserSessionData {
+ UserSessionModel that = (UserSessionModel) o;
+ return that.getId().equals(getId());
+ }
+
+ @Override
+ public int hashCode() {
+ return getId().hashCode();
+ }
+
+ protected static class PersistentUserSessionData {
@JsonProperty("brokerSessionId")
private String brokerSessionId;
@@ -155,6 +216,9 @@ public class OfflineUserSessionAdapter implements UserSessionModel {
@JsonProperty("notes")
private Map notes;
+ @JsonProperty("state")
+ private State state;
+
public String getBrokerSessionId() {
return brokerSessionId;
}
@@ -211,5 +275,12 @@ public class OfflineUserSessionAdapter implements UserSessionModel {
this.notes = notes;
}
+ public State getState() {
+ return state;
+ }
+
+ public void setState(State state) {
+ this.state = state;
+ }
}
}
diff --git a/model/api/src/main/java/org/keycloak/models/OfflineUserSessionModel.java b/model/api/src/main/java/org/keycloak/models/session/PersistentUserSessionModel.java
similarity index 58%
rename from model/api/src/main/java/org/keycloak/models/OfflineUserSessionModel.java
rename to model/api/src/main/java/org/keycloak/models/session/PersistentUserSessionModel.java
index 9907783899..dc14e98173 100644
--- a/model/api/src/main/java/org/keycloak/models/OfflineUserSessionModel.java
+++ b/model/api/src/main/java/org/keycloak/models/session/PersistentUserSessionModel.java
@@ -1,11 +1,13 @@
-package org.keycloak.models;
+package org.keycloak.models.session;
/**
* @author Marek Posolda
*/
-public class OfflineUserSessionModel {
+public class PersistentUserSessionModel {
private String userSessionId;
+ private int lastSessionRefresh;
+
private String data;
public String getUserSessionId() {
@@ -16,6 +18,15 @@ public class OfflineUserSessionModel {
this.userSessionId = userSessionId;
}
+ public int getLastSessionRefresh() {
+ return lastSessionRefresh;
+ }
+
+ public void setLastSessionRefresh(int lastSessionRefresh) {
+ this.lastSessionRefresh = lastSessionRefresh;
+ }
+
+
public String getData() {
return data;
}
diff --git a/model/api/src/main/java/org/keycloak/models/session/UserSessionPersisterProvider.java b/model/api/src/main/java/org/keycloak/models/session/UserSessionPersisterProvider.java
new file mode 100644
index 0000000000..4b3355e9d3
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/session/UserSessionPersisterProvider.java
@@ -0,0 +1,43 @@
+package org.keycloak.models.session;
+
+import java.util.List;
+
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.provider.Provider;
+
+/**
+ * @author Marek Posolda
+ */
+public interface UserSessionPersisterProvider extends Provider {
+
+ // Persist just userSession. Not it's clientSessions
+ void createUserSession(UserSessionModel userSession, boolean offline);
+
+ // Assuming that corresponding userSession is already persisted
+ void createClientSession(ClientSessionModel clientSession, boolean offline);
+
+ void updateUserSession(UserSessionModel userSession, boolean offline);
+
+ // Called during logout (for online session) or during periodic expiration. It will remove all corresponding clientSessions too
+ void removeUserSession(String userSessionId, boolean offline);
+
+ // Called during revoke. It will remove userSession too if this was last clientSession attached to it
+ void removeClientSession(String clientSessionId, boolean offline);
+
+ void onRealmRemoved(RealmModel realm);
+ void onClientRemoved(RealmModel realm, ClientModel client);
+ void onUserRemoved(RealmModel realm, UserModel user);
+
+ // Called at startup to remove userSessions without any clientSession
+ void clearDetachedUserSessions();
+
+ // Called during startup. For each userSession, it loads also clientSessions
+ List loadUserSessions(int firstResult, int maxResults, boolean offline);
+
+ int getUserSessionsCount(boolean offline);
+
+}
diff --git a/model/api/src/main/java/org/keycloak/models/session/UserSessionPersisterProviderFactory.java b/model/api/src/main/java/org/keycloak/models/session/UserSessionPersisterProviderFactory.java
new file mode 100644
index 0000000000..39e7b2e113
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/session/UserSessionPersisterProviderFactory.java
@@ -0,0 +1,9 @@
+package org.keycloak.models.session;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author Marek Posolda
+ */
+public interface UserSessionPersisterProviderFactory extends ProviderFactory {
+}
diff --git a/model/api/src/main/java/org/keycloak/models/session/UserSessionPersisterSpi.java b/model/api/src/main/java/org/keycloak/models/session/UserSessionPersisterSpi.java
new file mode 100644
index 0000000000..cb66fa2601
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/session/UserSessionPersisterSpi.java
@@ -0,0 +1,31 @@
+package org.keycloak.models.session;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author Marek Posolda
+ */
+public class UserSessionPersisterSpi implements Spi {
+
+ @Override
+ public boolean isInternal() {
+ return true;
+ }
+
+ @Override
+ public String getName() {
+ return "userSessionPersister";
+ }
+
+ @Override
+ public Class extends Provider> getProviderClass() {
+ return UserSessionPersisterProvider.class;
+ }
+
+ @Override
+ public Class extends ProviderFactory> getProviderFactoryClass() {
+ return UserSessionPersisterProviderFactory.class;
+ }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index de062d58b5..96b47cb1a4 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -10,8 +10,8 @@ import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.ModelException;
import org.keycloak.models.OTPPolicy;
-import org.keycloak.models.OfflineClientSessionModel;
-import org.keycloak.models.OfflineUserSessionModel;
+import org.keycloak.models.session.PersistentClientSessionModel;
+import org.keycloak.models.session.PersistentUserSessionModel;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredActionProviderModel;
@@ -511,13 +511,13 @@ public class ModelToRepresentation {
return rep;
}
- public static OfflineUserSessionRepresentation toRepresentation(RealmModel realm, OfflineUserSessionModel model, Collection clientSessions) {
+ public static OfflineUserSessionRepresentation toRepresentation(RealmModel realm, PersistentUserSessionModel model, Collection clientSessions) {
OfflineUserSessionRepresentation rep = new OfflineUserSessionRepresentation();
rep.setData(model.getData());
rep.setUserSessionId(model.getUserSessionId());
List clientSessionReps = new LinkedList<>();
- for (OfflineClientSessionModel clsm : clientSessions) {
+ for (PersistentClientSessionModel clsm : clientSessions) {
OfflineClientSessionRepresentation clrep = toRepresentation(realm, clsm);
clientSessionReps.add(clrep);
}
@@ -525,7 +525,7 @@ public class ModelToRepresentation {
return rep;
}
- public static OfflineClientSessionRepresentation toRepresentation(RealmModel realm, OfflineClientSessionModel model) {
+ public static OfflineClientSessionRepresentation toRepresentation(RealmModel realm, PersistentClientSessionModel model) {
OfflineClientSessionRepresentation rep = new OfflineClientSessionRepresentation();
String clientInternalId = model.getClientId();
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index 482918254d..62933a04c7 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -1,7 +1,7 @@
package org.keycloak.models.utils;
-import org.keycloak.models.OfflineClientSessionModel;
-import org.keycloak.models.OfflineUserSessionModel;
+import org.keycloak.models.session.PersistentClientSessionModel;
+import org.keycloak.models.session.PersistentUserSessionModel;
import org.keycloak.representations.idm.OfflineClientSessionRepresentation;
import org.keycloak.representations.idm.OfflineUserSessionRepresentation;
import org.keycloak.util.Base64;
@@ -985,11 +985,6 @@ public class RepresentationToModel {
user.addConsent(consentModel);
}
}
- if (userRep.getOfflineUserSessions() != null) {
- for (OfflineUserSessionRepresentation sessionRep : userRep.getOfflineUserSessions()) {
- importOfflineSession(session, newRealm, user, sessionRep);
- }
- }
if (userRep.getServiceAccountClientId() != null) {
String clientId = userRep.getServiceAccountClientId();
ClientModel client = clientMap.get(clientId);
@@ -1160,28 +1155,29 @@ public class RepresentationToModel {
return consentModel;
}
- public static void importOfflineSession(KeycloakSession session, RealmModel newRealm, UserModel user, OfflineUserSessionRepresentation sessionRep) {
- OfflineUserSessionModel model = new OfflineUserSessionModel();
- model.setUserSessionId(sessionRep.getUserSessionId());
- model.setData(sessionRep.getData());
- session.users().addOfflineUserSession(newRealm, user, model);
-
- for (OfflineClientSessionRepresentation csRep : sessionRep.getOfflineClientSessions()) {
- OfflineClientSessionModel csModel = new OfflineClientSessionModel();
- String clientId = csRep.getClient();
- ClientModel client = newRealm.getClientByClientId(clientId);
- if (client == null) {
- throw new RuntimeException("Unable to find client " + clientId + " referenced from offlineClientSession of user " + user.getUsername());
- }
- csModel.setClientId(client.getId());
- csModel.setUserId(user.getId());
- csModel.setClientSessionId(csRep.getClientSessionId());
- csModel.setUserSessionId(sessionRep.getUserSessionId());
- csModel.setData(csRep.getData());
-
- session.users().addOfflineClientSession(newRealm, csModel);
- }
- }
+ // TODO
+// public static void importOfflineSession(KeycloakSession session, RealmModel newRealm, UserModel user, OfflineUserSessionRepresentation sessionRep) {
+// PersistentUserSessionModel model = new PersistentUserSessionModel();
+// model.setUserSessionId(sessionRep.getUserSessionId());
+// model.setData(sessionRep.getData());
+// session.users().createOfflineUserSession(newRealm, user, model);
+//
+// for (OfflineClientSessionRepresentation csRep : sessionRep.getOfflineClientSessions()) {
+// PersistentClientSessionModel csModel = new PersistentClientSessionModel();
+// String clientId = csRep.getClient();
+// ClientModel client = newRealm.getClientByClientId(clientId);
+// if (client == null) {
+// throw new RuntimeException("Unable to find client " + clientId + " referenced from offlineClientSession of user " + user.getUsername());
+// }
+// csModel.setClientId(client.getId());
+// csModel.setUserId(user.getId());
+// csModel.setClientSessionId(csRep.getClientSessionId());
+// csModel.setUserSessionId(sessionRep.getUserSessionId());
+// csModel.setData(csRep.getData());
+//
+// session.users().createOfflineClientSession(newRealm, csModel);
+// }
+// }
public static AuthenticationFlowModel toModel(AuthenticationFlowRepresentation rep) {
AuthenticationFlowModel model = new AuthenticationFlowModel();
diff --git a/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java b/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
index 5f7303129e..4cd162bce6 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
@@ -1,15 +1,12 @@
package org.keycloak.models.utils;
import org.keycloak.models.ClientModel;
-import org.keycloak.models.OfflineClientSessionModel;
-import org.keycloak.models.OfflineUserSessionModel;
import org.keycloak.models.UserConsentModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel;
-import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
diff --git a/model/api/src/main/resources/META-INF/services/org.keycloak.models.session.UserSessionPersisterProviderFactory b/model/api/src/main/resources/META-INF/services/org.keycloak.models.session.UserSessionPersisterProviderFactory
new file mode 100644
index 0000000000..dc21d4d333
--- /dev/null
+++ b/model/api/src/main/resources/META-INF/services/org.keycloak.models.session.UserSessionPersisterProviderFactory
@@ -0,0 +1 @@
+org.keycloak.models.session.DisabledUserSessionPersisterProvider
\ No newline at end of file
diff --git a/model/api/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/model/api/src/main/resources/META-INF/services/org.keycloak.provider.Spi
index ba45379b08..be3982bce4 100755
--- a/model/api/src/main/resources/META-INF/services/org.keycloak.provider.Spi
+++ b/model/api/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -3,4 +3,5 @@ org.keycloak.mappers.UserFederationMapperSpi
org.keycloak.models.RealmSpi
org.keycloak.models.UserSessionSpi
org.keycloak.models.UserSpi
+org.keycloak.models.session.UserSessionPersisterSpi
org.keycloak.migration.MigrationSpi
\ No newline at end of file
diff --git a/model/file/src/main/java/org/keycloak/models/file/FileUserProvider.java b/model/file/src/main/java/org/keycloak/models/file/FileUserProvider.java
index 9f4080ab70..8edfe3ec2e 100755
--- a/model/file/src/main/java/org/keycloak/models/file/FileUserProvider.java
+++ b/model/file/src/main/java/org/keycloak/models/file/FileUserProvider.java
@@ -24,8 +24,8 @@ import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.ModelException;
-import org.keycloak.models.OfflineClientSessionModel;
-import org.keycloak.models.OfflineUserSessionModel;
+import org.keycloak.models.session.PersistentClientSessionModel;
+import org.keycloak.models.session.PersistentUserSessionModel;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredActionProviderModel;
@@ -35,8 +35,8 @@ import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider;
import org.keycloak.models.entities.FederatedIdentityEntity;
-import org.keycloak.models.entities.OfflineClientSessionEntity;
-import org.keycloak.models.entities.OfflineUserSessionEntity;
+import org.keycloak.models.entities.PersistentClientSessionEntity;
+import org.keycloak.models.entities.PersistentUserSessionEntity;
import org.keycloak.models.entities.UserEntity;
import org.keycloak.models.file.adapter.UserAdapter;
import org.keycloak.models.utils.CredentialValidation;
@@ -494,188 +494,4 @@ public class FileUserProvider implements UserProvider {
//throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
return null; // not supported yet
}
-
- @Override
- public void addOfflineUserSession(RealmModel realm, UserModel userModel, OfflineUserSessionModel userSession) {
- userModel = getUserById(userModel.getId(), realm);
- UserEntity userEntity = ((UserAdapter) userModel).getUserEntity();
-
- if (userEntity.getOfflineUserSessions() == null) {
- userEntity.setOfflineUserSessions(new ArrayList());
- }
-
- if (getUserSessionEntityById(userEntity, userSession.getUserSessionId()) != null) {
- throw new ModelDuplicateException("User session already exists with id " + userSession.getUserSessionId() + " for user " + userEntity.getUsername());
- }
-
- OfflineUserSessionEntity entity = new OfflineUserSessionEntity();
- entity.setUserSessionId(userSession.getUserSessionId());
- entity.setData(userSession.getData());
- entity.setOfflineClientSessions(new ArrayList());
- userEntity.getOfflineUserSessions().add(entity);
- }
-
- @Override
- public OfflineUserSessionModel getOfflineUserSession(RealmModel realm, UserModel userModel, String userSessionId) {
- userModel = getUserById(userModel.getId(), realm);
- UserEntity userEntity = ((UserAdapter) userModel).getUserEntity();
-
- OfflineUserSessionEntity entity = getUserSessionEntityById(userEntity, userSessionId);
- return entity==null ? null : toModel(entity);
- }
-
- @Override
- public Collection getOfflineUserSessions(RealmModel realm, UserModel userModel) {
- userModel = getUserById(userModel.getId(), realm);
- UserEntity user = ((UserAdapter) userModel).getUserEntity();
-
- if (user.getOfflineUserSessions()==null) {
- return Collections.emptyList();
- } else {
- List result = new ArrayList<>();
- for (OfflineUserSessionEntity entity : user.getOfflineUserSessions()) {
- result.add(toModel(entity));
- }
- return result;
- }
- }
-
- private OfflineUserSessionModel toModel(OfflineUserSessionEntity entity) {
- OfflineUserSessionModel model = new OfflineUserSessionModel();
- model.setUserSessionId(entity.getUserSessionId());
- model.setData(entity.getData());
- return model;
- }
-
- @Override
- public boolean removeOfflineUserSession(RealmModel realm, UserModel userModel, String userSessionId) {
- userModel = getUserById(userModel.getId(), realm);
- UserEntity user = ((UserAdapter) userModel).getUserEntity();
-
- OfflineUserSessionEntity entity = getUserSessionEntityById(user, userSessionId);
- if (entity != null) {
- user.getOfflineUserSessions().remove(entity);
- return true;
- } else {
- return false;
- }
- }
-
- private OfflineUserSessionEntity getUserSessionEntityById(UserEntity user, String userSessionId) {
- if (user.getOfflineUserSessions() != null) {
- for (OfflineUserSessionEntity entity : user.getOfflineUserSessions()) {
- if (entity.getUserSessionId().equals(userSessionId)) {
- return entity;
- }
- }
- }
- return null;
- }
-
- @Override
- public void addOfflineClientSession(RealmModel realm, OfflineClientSessionModel clientSession) {
- UserModel userModel = getUserById(clientSession.getUserId(), realm);
- UserEntity user = ((UserAdapter) userModel).getUserEntity();
-
- OfflineUserSessionEntity userSessionEntity = getUserSessionEntityById(user, clientSession.getUserSessionId());
- if (userSessionEntity == null) {
- throw new ModelException("OfflineUserSession with ID " + clientSession.getUserSessionId() + " doesn't exist for user " + user.getUsername());
- }
-
- OfflineClientSessionEntity clEntity = new OfflineClientSessionEntity();
- clEntity.setClientSessionId(clientSession.getClientSessionId());
- clEntity.setClientId(clientSession.getClientId());
- clEntity.setData(clientSession.getData());
-
- userSessionEntity.getOfflineClientSessions().add(clEntity);
- }
-
- @Override
- public OfflineClientSessionModel getOfflineClientSession(RealmModel realm, UserModel userModel, String clientSessionId) {
- userModel = getUserById(userModel.getId(), realm);
- UserEntity user = ((UserAdapter) userModel).getUserEntity();
-
- if (user.getOfflineUserSessions() != null) {
- for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
- for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
- if (clSession.getClientSessionId().equals(clientSessionId)) {
- return toModel(clSession, userSession.getUserSessionId());
- }
- }
- }
- }
-
- return null;
- }
-
- private OfflineClientSessionModel toModel(OfflineClientSessionEntity cls, String userSessionId) {
- OfflineClientSessionModel model = new OfflineClientSessionModel();
- model.setClientSessionId(cls.getClientSessionId());
- model.setClientId(cls.getClientId());
- model.setData(cls.getData());
- model.setUserSessionId(userSessionId);
- return model;
- }
-
- @Override
- public Collection getOfflineClientSessions(RealmModel realm, UserModel userModel) {
- userModel = getUserById(userModel.getId(), realm);
- UserEntity user = ((UserAdapter) userModel).getUserEntity();
-
- List result = new ArrayList<>();
-
- if (user.getOfflineUserSessions() != null) {
- for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
- for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
- result.add(toModel(clSession, userSession.getUserSessionId()));
- }
- }
- }
-
- return result;
- }
-
- @Override
- public boolean removeOfflineClientSession(RealmModel realm, UserModel userModel, String clientSessionId) {
- userModel = getUserById(userModel.getId(), realm);
- UserEntity user = ((UserAdapter) userModel).getUserEntity();
-
- if (user.getOfflineUserSessions() != null) {
- for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
- for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
- if (clSession.getClientSessionId().equals(clientSessionId)) {
- userSession.getOfflineClientSessions().remove(clSession);
- return true;
- }
- }
- }
- }
-
- return false;
- }
-
- @Override
- public int getOfflineClientSessionsCount(RealmModel realm, ClientModel client) {
- return getOfflineClientSessions(realm, client, -1, -1).size();
- }
-
- @Override
- public Collection getOfflineClientSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
- List result = new LinkedList<>();
-
- List users = new ArrayList<>(inMemoryModel.getUsers(realm.getId()));
- users = sortedSubList(users, firstResult, maxResults);
-
- for (UserModel userModel : users) {
- UserEntity user = ((UserAdapter) userModel).getUserEntity();
- for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
- for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
- if (clSession.getClientId().equals(client.getId())) {
- result.add(toModel(clSession, userSession.getUserSessionId()));
- }
- }
- }
- }
- return result;
- }
}
diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java
index 2c233d1068..a4442c8029 100755
--- a/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java
+++ b/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java
@@ -22,10 +22,7 @@ import org.keycloak.models.ClientModel;
import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt;
import org.keycloak.models.ModelDuplicateException;
-import org.keycloak.models.ModelException;
import org.keycloak.models.OTPPolicy;
-import org.keycloak.models.OfflineClientSessionModel;
-import org.keycloak.models.OfflineUserSessionModel;
import org.keycloak.models.UserConsentModel;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
@@ -35,8 +32,6 @@ import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.entities.CredentialEntity;
import org.keycloak.models.entities.FederatedIdentityEntity;
-import org.keycloak.models.entities.OfflineClientSessionEntity;
-import org.keycloak.models.entities.OfflineUserSessionEntity;
import org.keycloak.models.entities.RoleEntity;
import org.keycloak.models.entities.UserEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
@@ -44,7 +39,6 @@ import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
import org.keycloak.util.Time;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
@@ -53,8 +47,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
-import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt;
-
/**
* UserModel for JSON persistence.
*
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheUserProvider.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheUserProvider.java
index 584e91f764..69fc5cf577 100644
--- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheUserProvider.java
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheUserProvider.java
@@ -4,6 +4,8 @@ import org.keycloak.models.*;
import org.keycloak.models.cache.CacheUserProvider;
import org.keycloak.models.cache.UserCache;
import org.keycloak.models.cache.entities.CachedUser;
+import org.keycloak.models.session.PersistentClientSessionModel;
+import org.keycloak.models.session.PersistentUserSessionModel;
import java.util.*;
@@ -121,7 +123,7 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
if (model == null) return null;
if (managedUsers.containsKey(id)) return managedUsers.get(id);
if (userInvalidations.containsKey(id)) return model;
- cached = new CachedUser(this, realm, model);
+ cached = new CachedUser(realm, model);
cache.addCachedUser(realm.getId(), cached);
} else if (managedUsers.containsKey(id)) {
return managedUsers.get(id);
@@ -146,7 +148,7 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
if (model == null) return null;
if (managedUsers.containsKey(model.getId())) return managedUsers.get(model.getId());
if (userInvalidations.containsKey(model.getId())) return model;
- cached = new CachedUser(this, realm, model);
+ cached = new CachedUser(realm, model);
cache.addCachedUser(realm.getId(), cached);
} else if (userInvalidations.containsKey(cached.getId())) {
return getDelegate().getUserById(cached.getId(), realm);
@@ -173,7 +175,7 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
UserModel model = getDelegate().getUserByEmail(email, realm);
if (model == null) return null;
if (userInvalidations.containsKey(model.getId())) return model;
- cached = new CachedUser(this, realm, model);
+ cached = new CachedUser(realm, model);
cache.addCachedUser(realm.getId(), cached);
} else if (userInvalidations.containsKey(cached.getId())) {
return getDelegate().getUserByEmail(email, realm);
@@ -328,94 +330,4 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
public void preRemove(ClientModel client, ProtocolMapperModel protocolMapper) {
getDelegate().preRemove(client, protocolMapper);
}
-
- @Override
- public void addOfflineUserSession(RealmModel realm, UserModel user, OfflineUserSessionModel offlineUserSession) {
- registerUserInvalidation(realm, user.getId());
- getDelegate().addOfflineUserSession(realm, user, offlineUserSession);
- }
-
- @Override
- public OfflineUserSessionModel getOfflineUserSession(RealmModel realm, UserModel user, String userSessionId) {
- if (isRegisteredForInvalidation(realm, user.getId())) {
- return getDelegate().getOfflineUserSession(realm, user, userSessionId);
- }
-
- CachedUser cachedUser = cache.getCachedUser(realm.getId(), user.getId());
- if (cachedUser == null) {
- return getDelegate().getOfflineUserSession(realm, user, userSessionId);
- } else {
- return cachedUser.getOfflineUserSessions().get(userSessionId);
- }
- }
-
- @Override
- public Collection getOfflineUserSessions(RealmModel realm, UserModel user) {
- if (isRegisteredForInvalidation(realm, user.getId())) {
- return getDelegate().getOfflineUserSessions(realm, user);
- }
-
- CachedUser cachedUser = cache.getCachedUser(realm.getId(), user.getId());
- if (cachedUser == null) {
- return getDelegate().getOfflineUserSessions(realm, user);
- } else {
- return cachedUser.getOfflineUserSessions().values();
- }
- }
-
- @Override
- public boolean removeOfflineUserSession(RealmModel realm, UserModel user, String userSessionId) {
- registerUserInvalidation(realm, user.getId());
- return getDelegate().removeOfflineUserSession(realm, user, userSessionId);
- }
-
- @Override
- public void addOfflineClientSession(RealmModel realm, OfflineClientSessionModel offlineClientSession) {
- registerUserInvalidation(realm, offlineClientSession.getUserId());
- getDelegate().addOfflineClientSession(realm, offlineClientSession);
- }
-
- @Override
- public OfflineClientSessionModel getOfflineClientSession(RealmModel realm, UserModel user, String clientSessionId) {
- if (isRegisteredForInvalidation(realm, user.getId())) {
- return getDelegate().getOfflineClientSession(realm, user, clientSessionId);
- }
-
- CachedUser cachedUser = cache.getCachedUser(realm.getId(), user.getId());
- if (cachedUser == null) {
- return getDelegate().getOfflineClientSession(realm, user, clientSessionId);
- } else {
- return cachedUser.getOfflineClientSessions().get(clientSessionId);
- }
- }
-
- @Override
- public Collection getOfflineClientSessions(RealmModel realm, UserModel user) {
- if (isRegisteredForInvalidation(realm, user.getId())) {
- return getDelegate().getOfflineClientSessions(realm, user);
- }
-
- CachedUser cachedUser = cache.getCachedUser(realm.getId(), user.getId());
- if (cachedUser == null) {
- return getDelegate().getOfflineClientSessions(realm, user);
- } else {
- return cachedUser.getOfflineClientSessions().values();
- }
- }
-
- @Override
- public boolean removeOfflineClientSession(RealmModel realm, UserModel user, String clientSessionId) {
- registerUserInvalidation(realm, user.getId());
- return getDelegate().removeOfflineClientSession(realm, user, clientSessionId);
- }
-
- @Override
- public int getOfflineClientSessionsCount(RealmModel realm, ClientModel client) {
- return getDelegate().getOfflineClientSessionsCount(realm, client);
- }
-
- @Override
- public Collection getOfflineClientSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
- return getDelegate().getOfflineClientSessions(realm, client, firstResult, maxResults);
- }
}
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java
index 24e866a4ae..0083856d14 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java
@@ -1,20 +1,15 @@
package org.keycloak.models.cache.entities;
-import org.keycloak.models.OfflineClientSessionModel;
-import org.keycloak.models.OfflineUserSessionModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel;
-import org.keycloak.models.cache.CacheUserProvider;
import org.keycloak.util.MultivaluedHashMap;
import java.io.Serializable;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
-import java.util.Map;
import java.util.Set;
/**
@@ -30,18 +25,16 @@ public class CachedUser implements Serializable {
private String lastName;
private String email;
private boolean emailVerified;
- private List credentials = new LinkedList();
+ private List credentials = new LinkedList<>();
private boolean enabled;
private boolean totp;
private String federationLink;
private String serviceAccountClientLink;
private MultivaluedHashMap attributes = new MultivaluedHashMap<>();
private Set requiredActions = new HashSet<>();
- private Set roleMappings = new HashSet();
- private Map offlineUserSessions = new HashMap<>();
- private Map offlineClientSessions = new HashMap<>();
+ private Set roleMappings = new HashSet<>();
- public CachedUser(CacheUserProvider cacheUserProvider, RealmModel realm, UserModel user) {
+ public CachedUser(RealmModel realm, UserModel user) {
this.id = user.getId();
this.realm = realm.getId();
this.username = user.getUsername();
@@ -60,12 +53,6 @@ public class CachedUser implements Serializable {
for (RoleModel role : user.getRoleMappings()) {
roleMappings.add(role.getId());
}
- for (OfflineUserSessionModel offlineSession : cacheUserProvider.getDelegate().getOfflineUserSessions(realm, user)) {
- offlineUserSessions.put(offlineSession.getUserSessionId(), offlineSession);
- }
- for (OfflineClientSessionModel offlineSession : cacheUserProvider.getDelegate().getOfflineClientSessions(realm, user)) {
- offlineClientSessions.put(offlineSession.getClientSessionId(), offlineSession);
- }
}
public String getId() {
@@ -131,12 +118,4 @@ public class CachedUser implements Serializable {
public String getServiceAccountClientLink() {
return serviceAccountClientLink;
}
-
- public Map getOfflineUserSessions() {
- return offlineUserSessions;
- }
-
- public Map getOfflineClientSessions() {
- return offlineClientSessions;
- }
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaKeycloakTransaction.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaKeycloakTransaction.java
deleted file mode 100755
index f19cffa84d..0000000000
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaKeycloakTransaction.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package org.keycloak.models.jpa;
-
-import org.keycloak.models.KeycloakTransaction;
-
-import javax.persistence.EntityManager;
-import javax.persistence.PersistenceException;
-
-/**
- * @author Bill Burke
- * @version $Revision: 1 $
- */
-public class JpaKeycloakTransaction implements KeycloakTransaction {
-
- protected EntityManager em;
-
- public JpaKeycloakTransaction(EntityManager em) {
- this.em = em;
- }
-
- @Override
- public void begin() {
- em.getTransaction().begin();
- }
-
- @Override
- public void commit() {
- try {
- em.getTransaction().commit();
- } catch (PersistenceException e) {
- throw PersistenceExceptionConverter.convert(e.getCause() != null ? e.getCause() : e);
- }
- }
-
- @Override
- public void rollback() {
- em.getTransaction().rollback();
- }
-
- @Override
- public void setRollbackOnly() {
- em.getTransaction().setRollbackOnly();
- }
-
- @Override
- public boolean getRollbackOnly() {
- return em.getTransaction().getRollbackOnly();
- }
-
- @Override
- public boolean isActive() {
- return em.getTransaction().isActive();
- }
-}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
index 563e89866b..d4d533a9dc 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
@@ -4,8 +4,6 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.OfflineClientSessionModel;
-import org.keycloak.models.OfflineUserSessionModel;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredActionProviderModel;
@@ -15,21 +13,15 @@ import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider;
import org.keycloak.models.jpa.entities.FederatedIdentityEntity;
-import org.keycloak.models.jpa.entities.OfflineClientSessionEntity;
-import org.keycloak.models.jpa.entities.OfflineUserSessionEntity;
import org.keycloak.models.jpa.entities.UserAttributeEntity;
import org.keycloak.models.jpa.entities.UserEntity;
import org.keycloak.models.utils.CredentialValidation;
import org.keycloak.models.utils.KeycloakModelUtils;
import javax.persistence.EntityManager;
-import javax.persistence.Query;
import javax.persistence.TypedQuery;
import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
import java.util.HashSet;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -175,10 +167,6 @@ public class JpaUserProvider implements UserProvider {
.setParameter("realmId", realm.getId()).executeUpdate();
num = em.createNamedQuery("deleteUserAttributesByRealm")
.setParameter("realmId", realm.getId()).executeUpdate();
- num = em.createNamedQuery("deleteOfflineClientSessionsByRealm")
- .setParameter("realmId", realm.getId()).executeUpdate();
- num = em.createNamedQuery("deleteOfflineUserSessionsByRealm")
- .setParameter("realmId", realm.getId()).executeUpdate();
num = em.createNamedQuery("deleteUsersByRealm")
.setParameter("realmId", realm.getId()).executeUpdate();
}
@@ -205,14 +193,6 @@ public class JpaUserProvider implements UserProvider {
.setParameter("realmId", realm.getId())
.setParameter("link", link.getId())
.executeUpdate();
- num = em.createNamedQuery("deleteOfflineClientSessionsByRealmAndLink")
- .setParameter("realmId", realm.getId())
- .setParameter("link", link.getId())
- .executeUpdate();
- num = em.createNamedQuery("deleteOfflineUserSessionsByRealmAndLink")
- .setParameter("realmId", realm.getId())
- .setParameter("link", link.getId())
- .executeUpdate();
num = em.createNamedQuery("deleteUsersByRealmAndLink")
.setParameter("realmId", realm.getId())
.setParameter("link", link.getId())
@@ -230,8 +210,6 @@ public class JpaUserProvider implements UserProvider {
em.createNamedQuery("deleteUserConsentProtMappersByClient").setParameter("clientId", client.getId()).executeUpdate();
em.createNamedQuery("deleteUserConsentRolesByClient").setParameter("clientId", client.getId()).executeUpdate();
em.createNamedQuery("deleteUserConsentsByClient").setParameter("clientId", client.getId()).executeUpdate();
- em.createNamedQuery("deleteOfflineClientSessionsByClient").setParameter("clientId", client.getId()).executeUpdate();
- em.createNamedQuery("deleteDetachedOfflineUserSessions").executeUpdate();
}
@Override
@@ -479,167 +457,4 @@ public class JpaUserProvider implements UserProvider {
// Not supported yet
return null;
}
-
- @Override
- public void addOfflineUserSession(RealmModel realm, UserModel user, OfflineUserSessionModel offlineUserSession) {
- UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
-
- OfflineUserSessionEntity entity = new OfflineUserSessionEntity();
- entity.setUser(userEntity);
- entity.setUserSessionId(offlineUserSession.getUserSessionId());
- entity.setData(offlineUserSession.getData());
- em.persist(entity);
- userEntity.getOfflineUserSessions().add(entity);
- em.flush();
- }
-
- @Override
- public OfflineUserSessionModel getOfflineUserSession(RealmModel realm, UserModel user, String userSessionId) {
- UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
-
- for (OfflineUserSessionEntity entity : userEntity.getOfflineUserSessions()) {
- if (entity.getUserSessionId().equals(userSessionId)) {
- return toModel(entity);
- }
- }
- return null;
- }
-
- private OfflineUserSessionModel toModel(OfflineUserSessionEntity entity) {
- OfflineUserSessionModel model = new OfflineUserSessionModel();
- model.setUserSessionId(entity.getUserSessionId());
- model.setData(entity.getData());
- return model;
- }
-
- @Override
- public Collection getOfflineUserSessions(RealmModel realm, UserModel user) {
- UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
-
- List result = new LinkedList<>();
- for (OfflineUserSessionEntity entity : userEntity.getOfflineUserSessions()) {
- result.add(toModel(entity));
- }
- return result;
- }
-
- @Override
- public boolean removeOfflineUserSession(RealmModel realm, UserModel user, String userSessionId) {
- UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
-
- OfflineUserSessionEntity found = null;
- for (OfflineUserSessionEntity session : userEntity.getOfflineUserSessions()) {
- if (session.getUserSessionId().equals(userSessionId)) {
- found = session;
- break;
- }
- }
-
- if (found == null) {
- return false;
- } else {
- userEntity.getOfflineUserSessions().remove(found);
- em.remove(found);
- em.flush();
- return true;
- }
- }
-
- @Override
- public void addOfflineClientSession(RealmModel realm, OfflineClientSessionModel offlineClientSession) {
- UserEntity userEntity = em.getReference(UserEntity.class, offlineClientSession.getUserId());
-
- OfflineClientSessionEntity entity = new OfflineClientSessionEntity();
- entity.setUser(userEntity);
- entity.setClientSessionId(offlineClientSession.getClientSessionId());
- entity.setUserSessionId(offlineClientSession.getUserSessionId());
- entity.setClientId(offlineClientSession.getClientId());
- entity.setData(offlineClientSession.getData());
- em.persist(entity);
- userEntity.getOfflineClientSessions().add(entity);
- em.flush();
- }
-
- @Override
- public OfflineClientSessionModel getOfflineClientSession(RealmModel realm, UserModel user, String clientSessionId) {
- UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
-
- for (OfflineClientSessionEntity entity : userEntity.getOfflineClientSessions()) {
- if (entity.getClientSessionId().equals(clientSessionId)) {
- return toModel(entity);
- }
- }
- return null;
- }
-
- private OfflineClientSessionModel toModel(OfflineClientSessionEntity entity) {
- OfflineClientSessionModel model = new OfflineClientSessionModel();
- model.setClientSessionId(entity.getClientSessionId());
- model.setClientId(entity.getClientId());
- model.setUserId(entity.getUser().getId());
- model.setUserSessionId(entity.getUserSessionId());
- model.setData(entity.getData());
- return model;
- }
-
- @Override
- public Collection getOfflineClientSessions(RealmModel realm, UserModel user) {
- UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
-
- List result = new LinkedList<>();
- for (OfflineClientSessionEntity entity : userEntity.getOfflineClientSessions()) {
- result.add(toModel(entity));
- }
- return result;
- }
-
- @Override
- public boolean removeOfflineClientSession(RealmModel realm, UserModel user, String clientSessionId) {
- UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
-
- OfflineClientSessionEntity found = null;
- for (OfflineClientSessionEntity session : userEntity.getOfflineClientSessions()) {
- if (session.getClientSessionId().equals(clientSessionId)) {
- found = session;
- break;
- }
- }
-
- if (found == null) {
- return false;
- } else {
- userEntity.getOfflineClientSessions().remove(found);
- em.remove(found);
- em.flush();
- return true;
- }
- }
-
- @Override
- public int getOfflineClientSessionsCount(RealmModel realm, ClientModel client) {
- Query query = em.createNamedQuery("findOfflineClientSessionsCountByClient");
- query.setParameter("clientId", client.getId());
- Number n = (Number) query.getSingleResult();
- return n.intValue();
- }
-
- @Override
- public Collection getOfflineClientSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
- TypedQuery query = em.createNamedQuery("findOfflineClientSessionsByClient", OfflineClientSessionEntity.class);
- query.setParameter("clientId", client.getId());
-
- if (firstResult != -1) {
- query.setFirstResult(firstResult);
- }
- if (maxResults != -1) {
- query.setMaxResults(maxResults);
- }
-
- List results = query.getResultList();
- Set set = new HashSet<>();
- for (OfflineClientSessionEntity entity : results) {
- set.add(toModel(entity));
- }
- return set;
- }
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
index 9c75057e4d..9757d5bb53 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
@@ -2,8 +2,6 @@ package org.keycloak.models.jpa;
import org.keycloak.models.ClientModel;
import org.keycloak.models.OTPPolicy;
-import org.keycloak.models.OfflineClientSessionModel;
-import org.keycloak.models.OfflineUserSessionModel;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.UserConsentModel;
import org.keycloak.models.ModelDuplicateException;
@@ -16,8 +14,6 @@ import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.jpa.entities.CredentialEntity;
-import org.keycloak.models.jpa.entities.OfflineClientSessionEntity;
-import org.keycloak.models.jpa.entities.OfflineUserSessionEntity;
import org.keycloak.models.jpa.entities.UserConsentEntity;
import org.keycloak.models.jpa.entities.UserConsentProtocolMapperEntity;
import org.keycloak.models.jpa.entities.UserConsentRoleEntity;
@@ -37,11 +33,8 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
-import java.util.Collection;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/OfflineClientSessionEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/OfflineClientSessionEntity.java
deleted file mode 100644
index e1c636a36f..0000000000
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/OfflineClientSessionEntity.java
+++ /dev/null
@@ -1,83 +0,0 @@
-package org.keycloak.models.jpa.entities;
-
-import javax.persistence.Column;
-import javax.persistence.Entity;
-import javax.persistence.FetchType;
-import javax.persistence.Id;
-import javax.persistence.JoinColumn;
-import javax.persistence.ManyToOne;
-import javax.persistence.NamedQueries;
-import javax.persistence.NamedQuery;
-import javax.persistence.Table;
-
-/**
- * @author Marek Posolda
- */
-@NamedQueries({
- @NamedQuery(name="deleteOfflineClientSessionsByRealm", query="delete from OfflineClientSessionEntity sess where sess.user IN (select u from UserEntity u where u.realmId=:realmId)"),
- @NamedQuery(name="deleteOfflineClientSessionsByRealmAndLink", query="delete from OfflineClientSessionEntity sess where sess.user IN (select u from UserEntity u where u.realmId=:realmId and u.federationLink=:link)"),
- @NamedQuery(name="deleteOfflineClientSessionsByClient", query="delete from OfflineClientSessionEntity sess where sess.clientId=:clientId"),
- @NamedQuery(name="findOfflineClientSessionsCountByClient", query="select count(sess) from OfflineClientSessionEntity sess where sess.clientId=:clientId"),
- @NamedQuery(name="findOfflineClientSessionsByClient", query="select sess from OfflineClientSessionEntity sess where sess.clientId=:clientId order by sess.user.username")
-})
-@Table(name="OFFLINE_CLIENT_SESSION")
-@Entity
-public class OfflineClientSessionEntity {
-
- @Id
- @Column(name="CLIENT_SESSION_ID", length = 36)
- protected String clientSessionId;
-
- @Column(name="USER_SESSION_ID", length = 36)
- protected String userSessionId;
-
- @Column(name="CLIENT_ID", length = 36)
- protected String clientId;
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name="USER_ID")
- protected UserEntity user;
-
- @Column(name="DATA")
- protected String data;
-
- public String getClientSessionId() {
- return clientSessionId;
- }
-
- public void setClientSessionId(String clientSessionId) {
- this.clientSessionId = clientSessionId;
- }
-
- public String getUserSessionId() {
- return userSessionId;
- }
-
- public void setUserSessionId(String userSessionId) {
- this.userSessionId = userSessionId;
- }
-
- public String getClientId() {
- return clientId;
- }
-
- public void setClientId(String clientId) {
- this.clientId = clientId;
- }
-
- public UserEntity getUser() {
- return user;
- }
-
- public void setUser(UserEntity user) {
- this.user = user;
- }
-
- public String getData() {
- return data;
- }
-
- public void setData(String data) {
- this.data = data;
- }
-}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/OfflineUserSessionEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/OfflineUserSessionEntity.java
deleted file mode 100644
index d2726fa674..0000000000
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/OfflineUserSessionEntity.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package org.keycloak.models.jpa.entities;
-
-import javax.persistence.Column;
-import javax.persistence.Entity;
-import javax.persistence.FetchType;
-import javax.persistence.Id;
-import javax.persistence.JoinColumn;
-import javax.persistence.ManyToOne;
-import javax.persistence.NamedQueries;
-import javax.persistence.NamedQuery;
-import javax.persistence.Table;
-
-/**
- * @author Marek Posolda
- */
-@NamedQueries({
- @NamedQuery(name="deleteOfflineUserSessionsByRealm", query="delete from OfflineUserSessionEntity sess where sess.user IN (select u from UserEntity u where u.realmId=:realmId)"),
- @NamedQuery(name="deleteOfflineUserSessionsByRealmAndLink", query="delete from OfflineUserSessionEntity sess where sess.user IN (select u from UserEntity u where u.realmId=:realmId and u.federationLink=:link)"),
- @NamedQuery(name="deleteDetachedOfflineUserSessions", query="delete from OfflineUserSessionEntity sess where sess.userSessionId NOT IN (select c.userSessionId from OfflineClientSessionEntity c)")
-})
-@Table(name="OFFLINE_USER_SESSION")
-@Entity
-public class OfflineUserSessionEntity {
-
- @Id
- @Column(name="USER_SESSION_ID", length = 36)
- protected String userSessionId;
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name="USER_ID")
- protected UserEntity user;
-
- @Column(name="DATA")
- protected String data;
-
- public String getUserSessionId() {
- return userSessionId;
- }
-
- public void setUserSessionId(String userSessionId) {
- this.userSessionId = userSessionId;
- }
-
- public UserEntity getUser() {
- return user;
- }
-
- public void setUser(UserEntity user) {
- this.user = user;
- }
-
- public String getData() {
- return data;
- }
-
- public void setData(String data) {
- this.data = data;
- }
-}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
index 24d23dbb93..2da1641586 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
@@ -3,13 +3,9 @@ package org.keycloak.models.jpa.entities;
import org.keycloak.models.utils.KeycloakModelUtils;
import javax.persistence.CascadeType;
-import javax.persistence.CollectionTable;
import javax.persistence.Column;
-import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.Id;
-import javax.persistence.JoinColumn;
-import javax.persistence.MapKeyColumn;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
@@ -18,8 +14,6 @@ import javax.persistence.UniqueConstraint;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
/**
* @author Bill Burke
@@ -89,12 +83,6 @@ public class UserEntity {
@Column(name="SERVICE_ACCOUNT_CLIENT_LINK")
protected String serviceAccountClientLink;
- @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="user")
- protected Collection offlineUserSessions = new ArrayList<>();
-
- @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="user")
- protected Collection offlineClientSessions = new ArrayList<>();
-
public String getId() {
return id;
}
@@ -224,22 +212,6 @@ public class UserEntity {
this.serviceAccountClientLink = serviceAccountClientLink;
}
- public Collection getOfflineUserSessions() {
- return offlineUserSessions;
- }
-
- public void setOfflineUserSessions(Collection offlineUserSessions) {
- this.offlineUserSessions = offlineUserSessions;
- }
-
- public Collection getOfflineClientSessions() {
- return offlineClientSessions;
- }
-
- public void setOfflineClientSessions(Collection offlineClientSessions) {
- this.offlineClientSessions = offlineClientSessions;
- }
-
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProvider.java
new file mode 100644
index 0000000000..ffc04557b1
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProvider.java
@@ -0,0 +1,239 @@
+package org.keycloak.models.jpa.session;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.persistence.EntityManager;
+import javax.persistence.Query;
+import javax.persistence.TypedQuery;
+
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.session.PersistentClientSessionAdapter;
+import org.keycloak.models.session.PersistentClientSessionModel;
+import org.keycloak.models.session.PersistentUserSessionAdapter;
+import org.keycloak.models.session.PersistentUserSessionModel;
+import org.keycloak.models.session.UserSessionPersisterProvider;
+
+/**
+ * @author Marek Posolda
+ */
+public class JpaUserSessionPersisterProvider implements UserSessionPersisterProvider {
+
+ private final KeycloakSession session;
+ private final EntityManager em;
+
+ public JpaUserSessionPersisterProvider(KeycloakSession session, EntityManager em) {
+ this.session = session;
+ this.em = em;
+ }
+
+ @Override
+ public void createUserSession(UserSessionModel userSession, boolean offline) {
+ PersistentUserSessionAdapter adapter = new PersistentUserSessionAdapter(userSession);
+ PersistentUserSessionModel model = adapter.getUpdatedModel();
+
+ PersistentUserSessionEntity entity = new PersistentUserSessionEntity();
+ entity.setUserSessionId(model.getUserSessionId());
+ entity.setRealmId(adapter.getRealm().getId());
+ entity.setUserId(adapter.getUser().getId());
+ entity.setOffline(offline);
+ entity.setLastSessionRefresh(model.getLastSessionRefresh());
+ entity.setData(model.getData());
+ em.persist(entity);
+ em.flush();
+ }
+
+ @Override
+ public void createClientSession(ClientSessionModel clientSession, boolean offline) {
+ PersistentClientSessionAdapter adapter = new PersistentClientSessionAdapter(clientSession);
+ PersistentClientSessionModel model = adapter.getUpdatedModel();
+
+ PersistentClientSessionEntity entity = new PersistentClientSessionEntity();
+ entity.setClientSessionId(clientSession.getId());
+ entity.setClientId(clientSession.getClient().getId());
+ entity.setOffline(offline);
+ entity.setUserSessionId(clientSession.getUserSession().getId());
+ entity.setData(model.getData());
+ em.persist(entity);
+ em.flush();
+ }
+
+ @Override
+ public void updateUserSession(UserSessionModel userSession, boolean offline) {
+ PersistentUserSessionAdapter adapter;
+ if (userSession instanceof PersistentUserSessionAdapter) {
+ adapter = (PersistentUserSessionAdapter) userSession;
+ } else {
+ adapter = new PersistentUserSessionAdapter(userSession);
+ }
+
+ PersistentUserSessionModel model = adapter.getUpdatedModel();
+
+ PersistentUserSessionEntity entity = em.find(PersistentUserSessionEntity.class, new PersistentUserSessionEntity.Key(userSession.getId(), offline));
+ if (entity == null) {
+ throw new ModelException("UserSession with ID " + userSession.getId() + ", offline: " + offline + " not found");
+ }
+ entity.setLastSessionRefresh(model.getLastSessionRefresh());
+ entity.setData(model.getData());
+ }
+
+ @Override
+ public void removeUserSession(String userSessionId, boolean offline) {
+ em.createNamedQuery("deleteClientSessionsByUserSession")
+ .setParameter("userSessionId", userSessionId)
+ .setParameter("offline", offline)
+ .executeUpdate();
+
+ PersistentUserSessionEntity sessionEntity = em.find(PersistentUserSessionEntity.class, new PersistentUserSessionEntity.Key(userSessionId, offline));
+ if (sessionEntity != null) {
+ em.remove(sessionEntity);
+ em.flush();
+ }
+ }
+
+ @Override
+ public void removeClientSession(String clientSessionId, boolean offline) {
+ PersistentClientSessionEntity sessionEntity = em.find(PersistentClientSessionEntity.class, new PersistentClientSessionEntity.Key(clientSessionId, offline));
+ if (sessionEntity != null) {
+ em.remove(sessionEntity);
+
+ // Remove userSession if it was last clientSession
+ List clientSessions = getClientSessionsByUserSession(sessionEntity.getUserSessionId(), offline);
+ if (clientSessions.size() == 0) {
+ PersistentUserSessionEntity userSessionEntity = em.find(PersistentUserSessionEntity.class, new PersistentUserSessionEntity.Key(sessionEntity.getUserSessionId(), offline));
+ if (userSessionEntity != null) {
+ em.remove(userSessionEntity);
+ }
+ }
+
+ em.flush();
+ }
+ }
+
+ private List getClientSessionsByUserSession(String userSessionId, boolean offline) {
+ TypedQuery query = em.createNamedQuery("findClientSessionsByUserSession", PersistentClientSessionEntity.class);
+ query.setParameter("userSessionId", userSessionId);
+ query.setParameter("offline", offline);
+ return query.getResultList();
+ }
+
+
+
+ @Override
+ public void onRealmRemoved(RealmModel realm) {
+ em.createNamedQuery("deleteClientSessionsByRealm").setParameter("realmId", realm.getId()).executeUpdate();
+ em.createNamedQuery("deleteUserSessionsByRealm").setParameter("realmId", realm.getId()).executeUpdate();
+ }
+
+ @Override
+ public void onClientRemoved(RealmModel realm, ClientModel client) {
+ em.createNamedQuery("deleteClientSessionsByClient").setParameter("clientId", client.getId()).executeUpdate();
+ em.createNamedQuery("deleteDetachedUserSessions").executeUpdate();
+ }
+
+ @Override
+ public void onUserRemoved(RealmModel realm, UserModel user) {
+ em.createNamedQuery("deleteClientSessionsByUser").setParameter("userId", user.getId()).executeUpdate();
+ em.createNamedQuery("deleteUserSessionsByUser").setParameter("userId", user.getId()).executeUpdate();
+ }
+
+ @Override
+ public void clearDetachedUserSessions() {
+ em.createNamedQuery("deleteDetachedClientSessions").executeUpdate();
+ em.createNamedQuery("deleteDetachedUserSessions").executeUpdate();
+ }
+
+ @Override
+ public List loadUserSessions(int firstResult, int maxResults, boolean offline) {
+ TypedQuery query = em.createNamedQuery("findUserSessions", PersistentUserSessionEntity.class);
+ query.setParameter("offline", offline);
+
+ if (firstResult != -1) {
+ query.setFirstResult(firstResult);
+ }
+ if (maxResults != -1) {
+ query.setMaxResults(maxResults);
+ }
+
+ List results = query.getResultList();
+ List result = new ArrayList<>();
+ List userSessionIds = new ArrayList<>();
+ for (PersistentUserSessionEntity entity : results) {
+ result.add(toAdapter(entity));
+ userSessionIds.add(entity.getUserSessionId());
+ }
+
+ TypedQuery query2 = em.createNamedQuery("findClientSessionsByUserSessions", PersistentClientSessionEntity.class);
+ query2.setParameter("userSessionIds", userSessionIds);
+ query2.setParameter("offline", offline);
+ List clientSessions = query2.getResultList();
+
+ // Assume both userSessions and clientSessions ordered by userSessionId
+ int j=0;
+ for (UserSessionModel ss : result) {
+ PersistentUserSessionAdapter userSession = (PersistentUserSessionAdapter) ss;
+ List currentClientSessions = userSession.getClientSessions(); // This is empty now and we want to fill it
+
+ boolean next = true;
+ while (next && j clientSessions = new LinkedList<>();
+ return new PersistentUserSessionAdapter(model, realm, user, clientSessions);
+ }
+
+ private PersistentClientSessionAdapter toAdapter(RealmModel realm, PersistentUserSessionAdapter userSession, PersistentClientSessionEntity entity) {
+ ClientModel client = realm.getClientById(entity.getClientId());
+
+ PersistentClientSessionModel model = new PersistentClientSessionModel();
+ model.setClientSessionId(entity.getClientSessionId());
+ model.setClientId(entity.getClientId());
+ model.setUserSessionId(userSession.getId());
+ model.setUserId(userSession.getUser().getId());
+ model.setData(entity.getData());
+ return new PersistentClientSessionAdapter(model, realm, client, userSession);
+ }
+
+ @Override
+ public int getUserSessionsCount(boolean offline) {
+ Query query = em.createNamedQuery("findUserSessionsCount");
+ query.setParameter("offline", offline);
+ Number n = (Number) query.getSingleResult();
+ return n.intValue();
+ }
+
+ @Override
+ public void close() {
+
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProviderFactory.java b/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProviderFactory.java
new file mode 100644
index 0000000000..87a42cceab
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProviderFactory.java
@@ -0,0 +1,44 @@
+package org.keycloak.models.jpa.session;
+
+import javax.persistence.EntityManager;
+
+import org.keycloak.Config;
+import org.keycloak.connections.jpa.JpaConnectionProvider;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.session.UserSessionPersisterProvider;
+import org.keycloak.models.session.UserSessionPersisterProviderFactory;
+
+/**
+ * @author Marek Posolda
+ */
+public class JpaUserSessionPersisterProviderFactory implements UserSessionPersisterProviderFactory {
+
+ public static final String ID = "jpa";
+
+ @Override
+ public UserSessionPersisterProvider create(KeycloakSession session) {
+ EntityManager em = session.getProvider(JpaConnectionProvider.class).getEntityManager();
+ return new JpaUserSessionPersisterProvider(session, em);
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public String getId() {
+ return ID;
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/session/PersistentClientSessionEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/session/PersistentClientSessionEntity.java
new file mode 100644
index 0000000000..a11b87516a
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/session/PersistentClientSessionEntity.java
@@ -0,0 +1,132 @@
+package org.keycloak.models.jpa.session;
+
+import java.io.Serializable;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+
+/**
+ * @author Marek Posolda
+ */
+@NamedQueries({
+ @NamedQuery(name="deleteClientSessionsByRealm", query="delete from PersistentClientSessionEntity sess where sess.userSessionId in (select u from PersistentUserSessionEntity u where u.realmId=:realmId)"),
+ @NamedQuery(name="deleteClientSessionsByClient", query="delete from PersistentClientSessionEntity sess where sess.clientId=:clientId"),
+ @NamedQuery(name="deleteClientSessionsByUser", query="delete from PersistentClientSessionEntity sess where sess.userSessionId in (select u from PersistentUserSessionEntity u where u.userId=:userId)"),
+ @NamedQuery(name="deleteClientSessionsByUserSession", query="delete from PersistentClientSessionEntity sess where sess.userSessionId=:userSessionId and offline=:offline"),
+ @NamedQuery(name="deleteDetachedClientSessions", query="delete from PersistentClientSessionEntity sess where sess.userSessionId NOT IN (select u.userSessionId from PersistentUserSessionEntity u)"),
+ @NamedQuery(name="findClientSessionsByUserSession", query="select sess from PersistentClientSessionEntity sess where sess.userSessionId=:userSessionId and offline=:offline"),
+ @NamedQuery(name="findClientSessionsByUserSessions", query="select sess from PersistentClientSessionEntity sess where offline=:offline and sess.userSessionId IN (:userSessionIds) order by sess.userSessionId"),
+})
+@Table(name="OFFLINE_CLIENT_SESSION")
+@Entity
+@IdClass(PersistentClientSessionEntity.Key.class)
+public class PersistentClientSessionEntity {
+
+ @Id
+ @Column(name="CLIENT_SESSION_ID", length = 36)
+ protected String clientSessionId;
+
+ @Column(name = "USER_SESSION_ID", length = 36)
+ protected String userSessionId;
+
+ @Column(name="CLIENT_ID", length = 36)
+ protected String clientId;
+
+ @Id
+ @Column(name = "OFFLINE")
+ protected boolean offline;
+
+ @Column(name="DATA")
+ protected String data;
+
+ public String getClientSessionId() {
+ return clientSessionId;
+ }
+
+ public void setClientSessionId(String clientSessionId) {
+ this.clientSessionId = clientSessionId;
+ }
+
+ public String getUserSessionId() {
+ return userSessionId;
+ }
+
+ public void setUserSessionId(String userSessionId) {
+ this.userSessionId = userSessionId;
+ }
+
+ public String getClientId() {
+ return clientId;
+ }
+
+ public void setClientId(String clientId) {
+ this.clientId = clientId;
+ }
+
+ public boolean isOffline() {
+ return offline;
+ }
+
+ public void setOffline(boolean offline) {
+ this.offline = offline;
+ }
+
+ public String getData() {
+ return data;
+ }
+
+ public void setData(String data) {
+ this.data = data;
+ }
+
+ public static class Key implements Serializable {
+
+ protected String clientSessionId;
+
+ protected boolean offline;
+
+ public Key() {
+ }
+
+ public Key(String clientSessionId, boolean offline) {
+ this.clientSessionId = clientSessionId;
+ this.offline = offline;
+ }
+
+ public String getClientSessionId() {
+ return clientSessionId;
+ }
+
+ public boolean isOffline() {
+ return offline;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Key key = (Key) o;
+
+ if (this.clientSessionId != null ? !this.clientSessionId.equals(key.clientSessionId) : key.clientSessionId != null) return false;
+ if (offline != key.offline) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = this.clientSessionId != null ? this.clientSessionId.hashCode() : 0;
+ result = 31 * result + (offline ? 1 : 0);
+ return result;
+ }
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/session/PersistentUserSessionEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/session/PersistentUserSessionEntity.java
new file mode 100644
index 0000000000..f739091fe9
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/session/PersistentUserSessionEntity.java
@@ -0,0 +1,147 @@
+package org.keycloak.models.jpa.session;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+
+import org.keycloak.models.jpa.entities.UserEntity;
+
+/**
+ * @author Marek Posolda
+ */
+@NamedQueries({
+ @NamedQuery(name="deleteUserSessionsByRealm", query="delete from PersistentUserSessionEntity sess where sess.realmId=:realmId"),
+ @NamedQuery(name="deleteUserSessionsByUser", query="delete from PersistentUserSessionEntity sess where sess.userId=:userId"),
+ @NamedQuery(name="deleteDetachedUserSessions", query="delete from PersistentUserSessionEntity sess where sess.userSessionId NOT IN (select c.userSessionId from PersistentClientSessionEntity c)"),
+ @NamedQuery(name="findUserSessionsCount", query="select count(sess) from PersistentUserSessionEntity sess where offline=:offline"),
+ @NamedQuery(name="findUserSessions", query="select sess from PersistentUserSessionEntity sess where offline=:offline order by sess.userSessionId")
+
+})
+@Table(name="OFFLINE_USER_SESSION")
+@Entity
+@IdClass(PersistentUserSessionEntity.Key.class)
+public class PersistentUserSessionEntity {
+
+ @Id
+ @Column(name="USER_SESSION_ID", length = 36)
+ protected String userSessionId;
+
+ @Column(name = "REALM_ID", length = 36)
+ protected String realmId;
+
+ @Column(name="USER_ID", length = 36)
+ protected String userId;
+
+ @Column(name = "LAST_SESSION_REFRESH")
+ protected int lastSessionRefresh;
+
+ @Id
+ @Column(name = "OFFLINE")
+ protected boolean offline;
+
+ @Column(name="DATA")
+ protected String data;
+
+ public String getUserSessionId() {
+ return userSessionId;
+ }
+
+ public void setUserSessionId(String userSessionId) {
+ this.userSessionId = userSessionId;
+ }
+
+ public String getRealmId() {
+ return realmId;
+ }
+
+ public void setRealmId(String realmId) {
+ this.realmId = realmId;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public void setUserId(String userId) {
+ this.userId = userId;
+ }
+
+ public int getLastSessionRefresh() {
+ return lastSessionRefresh;
+ }
+
+ public void setLastSessionRefresh(int lastSessionRefresh) {
+ this.lastSessionRefresh = lastSessionRefresh;
+ }
+
+ public boolean isOffline() {
+ return offline;
+ }
+
+ public void setOffline(boolean offline) {
+ this.offline = offline;
+ }
+
+ public String getData() {
+ return data;
+ }
+
+ public void setData(String data) {
+ this.data = data;
+ }
+
+ public static class Key implements Serializable {
+
+ protected String userSessionId;
+
+ protected boolean offline;
+
+ public Key() {
+ }
+
+ public Key(String userSessionId, boolean offline) {
+ this.userSessionId = userSessionId;
+ this.offline = offline;
+ }
+
+ public String getUserSessionId() {
+ return userSessionId;
+ }
+
+ public boolean isOffline() {
+ return offline;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Key key = (Key) o;
+
+ if (this.userSessionId != null ? !this.userSessionId.equals(key.userSessionId) : key.userSessionId != null) return false;
+ if (offline != key.offline) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = this.userSessionId != null ? this.userSessionId.hashCode() : 0;
+ result = 31 * result + (offline ? 1 : 0);
+ return result;
+ }
+ }
+}
diff --git a/model/jpa/src/main/resources/META-INF/services/org.keycloak.models.session.UserSessionPersisterProviderFactory b/model/jpa/src/main/resources/META-INF/services/org.keycloak.models.session.UserSessionPersisterProviderFactory
new file mode 100644
index 0000000000..b478dda7fa
--- /dev/null
+++ b/model/jpa/src/main/resources/META-INF/services/org.keycloak.models.session.UserSessionPersisterProviderFactory
@@ -0,0 +1 @@
+org.keycloak.models.jpa.session.JpaUserSessionPersisterProviderFactory
\ No newline at end of file
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
index 214733af8d..358e6f2d28 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
@@ -10,10 +10,6 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.ModelDuplicateException;
-import org.keycloak.models.ModelException;
-import org.keycloak.models.OfflineClientSessionModel;
-import org.keycloak.models.OfflineUserSessionModel;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredActionProviderModel;
@@ -23,17 +19,13 @@ import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider;
import org.keycloak.models.entities.FederatedIdentityEntity;
-import org.keycloak.models.entities.OfflineClientSessionEntity;
-import org.keycloak.models.entities.OfflineUserSessionEntity;
import org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity;
import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
import org.keycloak.models.utils.CredentialValidation;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -407,36 +399,6 @@ public class MongoUserProvider implements UserProvider {
.and("clientId").is(client.getId())
.get();
getMongoStore().removeEntities(MongoUserConsentEntity.class, query, false, invocationContext);
-
- // Remove all offlineClientSessions
- query = new QueryBuilder()
- .and("offlineUserSessions.offlineClientSessions.clientId").is(client.getId())
- .get();
- List users = getMongoStore().loadEntities(MongoUserEntity.class, query, invocationContext);
- for (MongoUserEntity user : users) {
- boolean anyRemoved = false;
- for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
- for (OfflineClientSessionEntity clientSession : userSession.getOfflineClientSessions()) {
- if (clientSession.getClientId().equals(client.getId())) {
- userSession.getOfflineClientSessions().remove(clientSession);
- anyRemoved = true;
- break;
- }
- }
-
- // Check if it was last clientSession. Then remove userSession too
- if (userSession.getOfflineClientSessions().size() == 0) {
- user.getOfflineUserSessions().remove(userSession);
- anyRemoved = true;
- break;
- }
- }
-
- if (anyRemoved) {
- getMongoStore().updateEntity(user, invocationContext);
- }
-
- }
}
@Override
@@ -482,205 +444,4 @@ public class MongoUserProvider implements UserProvider {
// Not supported yet
return null;
}
-
- @Override
- public void addOfflineUserSession(RealmModel realm, UserModel userModel, OfflineUserSessionModel userSession) {
- MongoUserEntity user = getUserById(userModel.getId(), realm).getUser();
-
- if (user.getOfflineUserSessions() == null) {
- user.setOfflineUserSessions(new ArrayList());
- }
-
- if (getUserSessionEntityById(user, userSession.getUserSessionId()) != null) {
- throw new ModelDuplicateException("User session already exists with id " + userSession.getUserSessionId() + " for user " + user.getUsername());
- }
-
- OfflineUserSessionEntity entity = new OfflineUserSessionEntity();
- entity.setUserSessionId(userSession.getUserSessionId());
- entity.setData(userSession.getData());
- entity.setOfflineClientSessions(new ArrayList());
- user.getOfflineUserSessions().add(entity);
-
- getMongoStore().updateEntity(user, invocationContext);
- }
-
- private OfflineUserSessionModel toModel(OfflineUserSessionEntity entity) {
- OfflineUserSessionModel model = new OfflineUserSessionModel();
- model.setUserSessionId(entity.getUserSessionId());
- model.setData(entity.getData());
- return model;
- }
-
- private OfflineUserSessionEntity getUserSessionEntityById(MongoUserEntity user, String userSessionId) {
- if (user.getOfflineUserSessions() != null) {
- for (OfflineUserSessionEntity entity : user.getOfflineUserSessions()) {
- if (entity.getUserSessionId().equals(userSessionId)) {
- return entity;
- }
- }
- }
- return null;
- }
-
-
- @Override
- public OfflineUserSessionModel getOfflineUserSession(RealmModel realm, UserModel userModel, String userSessionId) {
- MongoUserEntity user = getUserById(userModel.getId(), realm).getUser();
-
- OfflineUserSessionEntity entity = getUserSessionEntityById(user, userSessionId);
- return entity==null ? null : toModel(entity);
- }
-
- @Override
- public Collection getOfflineUserSessions(RealmModel realm, UserModel userModel) {
- MongoUserEntity user = getUserById(userModel.getId(), realm).getUser();
-
- if (user.getOfflineUserSessions()==null) {
- return Collections.emptyList();
- } else {
- List result = new ArrayList<>();
- for (OfflineUserSessionEntity entity : user.getOfflineUserSessions()) {
- result.add(toModel(entity));
- }
- return result;
- }
- }
-
- @Override
- public boolean removeOfflineUserSession(RealmModel realm, UserModel userModel, String userSessionId) {
- MongoUserEntity user = getUserById(userModel.getId(), realm).getUser();
-
- OfflineUserSessionEntity entity = getUserSessionEntityById(user, userSessionId);
- if (entity != null) {
- user.getOfflineUserSessions().remove(entity);
- getMongoStore().updateEntity(user, invocationContext);
- return true;
- } else {
- return false;
- }
- }
-
- @Override
- public void addOfflineClientSession(RealmModel realm, OfflineClientSessionModel clientSession) {
- MongoUserEntity user = getUserById(clientSession.getUserId(), realm).getUser();
-
- OfflineUserSessionEntity userSessionEntity = getUserSessionEntityById(user, clientSession.getUserSessionId());
- if (userSessionEntity == null) {
- throw new ModelException("OfflineUserSession with ID " + clientSession.getUserSessionId() + " doesn't exist for user " + user.getUsername());
- }
-
- OfflineClientSessionEntity clEntity = new OfflineClientSessionEntity();
- clEntity.setClientSessionId(clientSession.getClientSessionId());
- clEntity.setClientId(clientSession.getClientId());
- clEntity.setData(clientSession.getData());
-
- userSessionEntity.getOfflineClientSessions().add(clEntity);
- getMongoStore().updateEntity(user, invocationContext);
- }
-
- @Override
- public OfflineClientSessionModel getOfflineClientSession(RealmModel realm, UserModel userModel, String clientSessionId) {
- MongoUserEntity user = getUserById(userModel.getId(), realm).getUser();
-
- if (user.getOfflineUserSessions() != null) {
- for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
- for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
- if (clSession.getClientSessionId().equals(clientSessionId)) {
- return toModel(clSession, user.getId(), userSession.getUserSessionId());
- }
- }
- }
- }
-
- return null;
- }
-
- private OfflineClientSessionModel toModel(OfflineClientSessionEntity cls, String userId, String userSessionId) {
- OfflineClientSessionModel model = new OfflineClientSessionModel();
- model.setClientSessionId(cls.getClientSessionId());
- model.setClientId(cls.getClientId());
- model.setUserId(userId);
- model.setData(cls.getData());
- model.setUserSessionId(userSessionId);
- return model;
- }
-
- @Override
- public Collection getOfflineClientSessions(RealmModel realm, UserModel userModel) {
- MongoUserEntity user = getUserById(userModel.getId(), realm).getUser();
-
- List result = new ArrayList<>();
-
- if (user.getOfflineUserSessions() != null) {
- for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
- for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
- result.add(toModel(clSession, user.getId(), userSession.getUserSessionId()));
- }
- }
- }
-
- return result;
- }
-
- @Override
- public boolean removeOfflineClientSession(RealmModel realm, UserModel userModel, String clientSessionId) {
- MongoUserEntity user = getUserById(userModel.getId(), realm).getUser();
- boolean updated = false;
-
- if (user.getOfflineUserSessions() != null) {
- for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
- for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
- if (clSession.getClientSessionId().equals(clientSessionId)) {
- userSession.getOfflineClientSessions().remove(clSession);
- updated = true;
- break;
- }
- }
-
- if (updated && userSession.getOfflineClientSessions().isEmpty()) {
- user.getOfflineUserSessions().remove(userSession);
- }
-
- if (updated) {
- getMongoStore().updateEntity(user, invocationContext);
- return true;
- }
- }
- }
-
- return false;
- }
-
- @Override
- public int getOfflineClientSessionsCount(RealmModel realm, ClientModel client) {
- DBObject query = new QueryBuilder()
- .and("realmId").is(realm.getId())
- .and("offlineUserSessions.offlineClientSessions.clientId").is(client.getId())
- .get();
- return getMongoStore().countEntities(MongoUserEntity.class, query, invocationContext);
- }
-
- @Override
- public Collection getOfflineClientSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
- DBObject query = new QueryBuilder()
- .and("realmId").is(realm.getId())
- .and("offlineUserSessions.offlineClientSessions.clientId").is(client.getId())
- .get();
- DBObject sort = new BasicDBObject("username", 1);
- List users = getMongoStore().loadEntities(MongoUserEntity.class, query, sort, firstResult, maxResults, invocationContext);
-
- List result = new LinkedList<>();
- for (MongoUserEntity user : users) {
- for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
- for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
- if (clSession.getClientId().equals(client.getId())) {
- OfflineClientSessionModel model = toModel(clSession, user.getId(), userSession.getUserSessionId());
- result.add(model);
- }
- }
- }
- }
-
- return result;
- }
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserSessionPersisterProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserSessionPersisterProvider.java
new file mode 100644
index 0000000000..53917c2b11
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserSessionPersisterProvider.java
@@ -0,0 +1,276 @@
+package org.keycloak.models.mongo.keycloak.adapters;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import com.mongodb.BasicDBObject;
+import com.mongodb.DBObject;
+import com.mongodb.QueryBuilder;
+import org.keycloak.connections.mongo.api.MongoStore;
+import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.entities.PersistentClientSessionEntity;
+import org.keycloak.models.entities.PersistentUserSessionEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoOfflineUserSessionEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoOnlineUserSessionEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoUserSessionEntity;
+import org.keycloak.models.session.PersistentClientSessionAdapter;
+import org.keycloak.models.session.PersistentClientSessionModel;
+import org.keycloak.models.session.PersistentUserSessionAdapter;
+import org.keycloak.models.session.PersistentUserSessionModel;
+import org.keycloak.models.session.UserSessionPersisterProvider;
+
+/**
+ * @author Marek Posolda
+ */
+public class MongoUserSessionPersisterProvider implements UserSessionPersisterProvider {
+
+ private final MongoStoreInvocationContext invocationContext;
+ private final KeycloakSession session;
+
+ public MongoUserSessionPersisterProvider(KeycloakSession session, MongoStoreInvocationContext invocationContext) {
+ this.session = session;
+ this.invocationContext = invocationContext;
+ }
+
+ protected MongoStore getMongoStore() {
+ return invocationContext.getMongoStore();
+ }
+
+ private Class extends PersistentUserSessionEntity> getClazz(boolean offline) {
+ return offline ? MongoOfflineUserSessionEntity.class : MongoOnlineUserSessionEntity.class;
+ }
+
+ private MongoUserSessionEntity loadUserSession(String userSessionId, boolean offline) {
+ Class extends MongoUserSessionEntity> clazz = offline ? MongoOfflineUserSessionEntity.class : MongoOnlineUserSessionEntity.class;
+ return getMongoStore().loadEntity(clazz, userSessionId, invocationContext);
+ }
+
+ @Override
+ public void createUserSession(UserSessionModel userSession, boolean offline) {
+ PersistentUserSessionAdapter adapter = new PersistentUserSessionAdapter(userSession);
+ PersistentUserSessionModel model = adapter.getUpdatedModel();
+
+ MongoUserSessionEntity entity = offline ? new MongoOfflineUserSessionEntity() : new MongoOnlineUserSessionEntity();
+ entity.setId(model.getUserSessionId());
+ entity.setRealmId(adapter.getRealm().getId());
+ entity.setUserId(adapter.getUser().getId());
+ entity.setLastSessionRefresh(model.getLastSessionRefresh());
+ entity.setData(model.getData());
+ entity.setClientSessions(new ArrayList());
+ getMongoStore().insertEntity(entity, invocationContext);
+ }
+
+ @Override
+ public void createClientSession(ClientSessionModel clientSession, boolean offline) {
+ PersistentClientSessionAdapter adapter = new PersistentClientSessionAdapter(clientSession);
+ PersistentClientSessionModel model = adapter.getUpdatedModel();
+
+ MongoUserSessionEntity userSession = loadUserSession(model.getUserSessionId(), offline);
+ if (userSession == null) {
+ throw new ModelException("Not userSession found with ID " + clientSession.getUserSession().getId() + ". Requested by clientSession: " + clientSession.getId());
+ } else {
+ PersistentClientSessionEntity entity = new PersistentClientSessionEntity();
+ entity.setClientSessionId(clientSession.getId());
+ entity.setClientId(clientSession.getClient().getId());
+ entity.setData(model.getData());
+ userSession.getClientSessions().add(entity);
+ getMongoStore().updateEntity(userSession, invocationContext);
+ }
+ }
+
+ @Override
+ public void updateUserSession(UserSessionModel userSession, boolean offline) {
+ PersistentUserSessionAdapter adapter;
+ if (userSession instanceof PersistentUserSessionAdapter) {
+ adapter = (PersistentUserSessionAdapter) userSession;
+ } else {
+ adapter = new PersistentUserSessionAdapter(userSession);
+ }
+
+ PersistentUserSessionModel model = adapter.getUpdatedModel();
+
+ MongoUserSessionEntity entity = loadUserSession(model.getUserSessionId(), offline);
+ if (entity == null) {
+ throw new ModelException("UserSession with ID " + userSession.getId() + ", offline: " + offline + " not found");
+ }
+ entity.setLastSessionRefresh(model.getLastSessionRefresh());
+ entity.setData(model.getData());
+
+ getMongoStore().updateEntity(entity, invocationContext);
+ }
+
+ @Override
+ public void removeUserSession(String userSessionId, boolean offline) {
+ MongoUserSessionEntity entity = loadUserSession(userSessionId, offline);
+ if (entity != null) {
+ getMongoStore().removeEntity(entity, invocationContext);
+ }
+ }
+
+ @Override
+ public void removeClientSession(String clientSessionId, boolean offline) {
+ DBObject query = new QueryBuilder()
+ .and("clientSessions.clientSessionId").is(clientSessionId)
+ .get();
+ Class extends MongoUserSessionEntity> clazz = offline ? MongoOfflineUserSessionEntity.class : MongoOnlineUserSessionEntity.class;
+ MongoUserSessionEntity userSession = getMongoStore().loadSingleEntity(clazz, query, invocationContext);
+ if (userSession != null) {
+
+ PersistentClientSessionEntity found = null;
+ for (PersistentClientSessionEntity clientSession : userSession.getClientSessions()) {
+ if (clientSession.getClientSessionId().equals(clientSessionId)) {
+ found = clientSession;
+ break;
+ }
+ }
+
+ if (found != null) {
+ userSession.getClientSessions().remove(found);
+
+ // Remove userSession if it was last clientSession attached
+ if (userSession.getClientSessions().size() == 0) {
+ getMongoStore().removeEntity(userSession, invocationContext);
+ } else {
+ getMongoStore().updateEntity(userSession, invocationContext);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onRealmRemoved(RealmModel realm) {
+ DBObject query = new QueryBuilder()
+ .and("realmId").is(realm.getId())
+ .get();
+ getMongoStore().removeEntities(MongoOnlineUserSessionEntity.class, query, false, invocationContext);
+ getMongoStore().removeEntities(MongoOfflineUserSessionEntity.class, query, false, invocationContext);
+ }
+
+ @Override
+ public void onClientRemoved(RealmModel realm, ClientModel client) {
+ DBObject query = new QueryBuilder()
+ .and("clientSessions.clientId").is(client.getId())
+ .get();
+
+ List userSessions = getMongoStore().loadEntities(MongoOnlineUserSessionEntity.class, query, invocationContext);
+ for (MongoOnlineUserSessionEntity userSession : userSessions) {
+ removeClientSessionOfClient(userSession, client.getId());
+ }
+
+ List userSessions2 = getMongoStore().loadEntities(MongoOfflineUserSessionEntity.class, query, invocationContext);
+ for (MongoOfflineUserSessionEntity userSession : userSessions2) {
+ removeClientSessionOfClient(userSession, client.getId());
+ }
+ }
+
+ private void removeClientSessionOfClient(MongoUserSessionEntity userSession, String clientId) {
+ PersistentClientSessionEntity found = null;
+ for (PersistentClientSessionEntity clientSession : userSession.getClientSessions()) {
+ if (clientSession.getClientId().equals(clientId)) {
+ found = clientSession;
+ break;
+ }
+ }
+
+ if (found != null) {
+ userSession.getClientSessions().remove(found);
+
+ // Remove userSession if it was last clientSession attached
+ if (userSession.getClientSessions().size() == 0) {
+ getMongoStore().removeEntity(userSession, invocationContext);
+ } else {
+ getMongoStore().updateEntity(userSession, invocationContext);
+ }
+ }
+ }
+
+ @Override
+ public void onUserRemoved(RealmModel realm, UserModel user) {
+ DBObject query = new QueryBuilder()
+ .and("userId").is(user.getId())
+ .get();
+ getMongoStore().removeEntities(MongoOnlineUserSessionEntity.class, query, false, invocationContext);
+ getMongoStore().removeEntities(MongoOfflineUserSessionEntity.class, query, false, invocationContext);
+ }
+
+ @Override
+ public void clearDetachedUserSessions() {
+ DBObject query = new QueryBuilder()
+ .and("clientSessions").is(Collections.emptyList())
+ .get();
+ getMongoStore().removeEntities(MongoOnlineUserSessionEntity.class, query, false, invocationContext);
+ getMongoStore().removeEntities(MongoOfflineUserSessionEntity.class, query, false, invocationContext);
+ }
+
+ @Override
+ public int getUserSessionsCount(boolean offline) {
+ DBObject query = new QueryBuilder()
+ .get();
+
+ Class extends MongoUserSessionEntity> clazz = offline ? MongoOfflineUserSessionEntity.class : MongoOnlineUserSessionEntity.class;
+ return getMongoStore().countEntities(clazz, query, invocationContext);
+ }
+
+ @Override
+ public List loadUserSessions(int firstResult, int maxResults, boolean offline) {
+ DBObject query = new QueryBuilder()
+ .get();
+ DBObject sort = new BasicDBObject("id", 1);
+
+ Class extends MongoUserSessionEntity> clazz = offline ? MongoOfflineUserSessionEntity.class : MongoOnlineUserSessionEntity.class;
+
+ List extends MongoUserSessionEntity> entities = getMongoStore().loadEntities(clazz, query, sort, firstResult, maxResults, invocationContext);
+
+ List results = new LinkedList<>();
+ for (MongoUserSessionEntity entity : entities) {
+ PersistentUserSessionAdapter userSession = toAdapter(entity, offline);
+ results.add(userSession);
+ }
+ return results;
+ }
+
+ private PersistentUserSessionAdapter toAdapter(PersistentUserSessionEntity entity, boolean offline) {
+ RealmModel realm = session.realms().getRealm(entity.getRealmId());
+ UserModel user = session.users().getUserById(entity.getUserId(), realm);
+
+ PersistentUserSessionModel model = new PersistentUserSessionModel();
+ model.setUserSessionId(entity.getId());
+ model.setLastSessionRefresh(entity.getLastSessionRefresh());
+ model.setData(entity.getData());
+
+ List clientSessions = new LinkedList<>();
+ PersistentUserSessionAdapter userSessionAdapter = new PersistentUserSessionAdapter(model, realm, user, clientSessions);
+ for (PersistentClientSessionEntity clientSessEntity : entity.getClientSessions()) {
+ PersistentClientSessionAdapter clientSessAdapter = toAdapter(realm, userSessionAdapter, offline, clientSessEntity);
+ clientSessions.add(clientSessAdapter);
+ }
+
+ return userSessionAdapter;
+ }
+
+ private PersistentClientSessionAdapter toAdapter(RealmModel realm, PersistentUserSessionAdapter userSession, boolean offline, PersistentClientSessionEntity entity) {
+ ClientModel client = realm.getClientById(entity.getClientId());
+
+ PersistentClientSessionModel model = new PersistentClientSessionModel();
+ model.setClientSessionId(entity.getClientSessionId());
+ model.setClientId(entity.getClientId());
+ model.setUserSessionId(userSession.getId());
+ model.setUserId(userSession.getUser().getId());
+ model.setData(entity.getData());
+ return new PersistentClientSessionAdapter(model, realm, client, userSession);
+ }
+
+ @Override
+ public void close() {
+
+ }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserSessionPersisterProviderFactory.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserSessionPersisterProviderFactory.java
new file mode 100644
index 0000000000..c2950fbc66
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserSessionPersisterProviderFactory.java
@@ -0,0 +1,42 @@
+package org.keycloak.models.mongo.keycloak.adapters;
+
+import org.keycloak.Config;
+import org.keycloak.connections.mongo.MongoConnectionProvider;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.session.UserSessionPersisterProvider;
+import org.keycloak.models.session.UserSessionPersisterProviderFactory;
+
+/**
+ * @author Marek Posolda
+ */
+public class MongoUserSessionPersisterProviderFactory implements UserSessionPersisterProviderFactory {
+
+ public static final String ID = "mongo";
+
+ @Override
+ public UserSessionPersisterProvider create(KeycloakSession session) {
+ MongoConnectionProvider connection = session.getProvider(MongoConnectionProvider.class);
+ return new MongoUserSessionPersisterProvider(session, connection.getInvocationContext());
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public String getId() {
+ return ID;
+ }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
index 9f13e632dd..16c4431c20 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
@@ -8,8 +8,6 @@ import com.mongodb.QueryBuilder;
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
import org.keycloak.models.ClientModel;
import org.keycloak.models.OTPPolicy;
-import org.keycloak.models.OfflineClientSessionModel;
-import org.keycloak.models.OfflineUserSessionModel;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.UserConsentModel;
import org.keycloak.models.KeycloakSession;
@@ -22,19 +20,15 @@ import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.entities.CredentialEntity;
-import org.keycloak.models.entities.OfflineClientSessionEntity;
-import org.keycloak.models.entities.OfflineUserSessionEntity;
import org.keycloak.models.entities.UserConsentEntity;
import org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity;
import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
import org.keycloak.models.mongo.utils.MongoModelUtils;
-import org.keycloak.models.utils.HmacOTP;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
import org.keycloak.util.Time;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoOfflineUserSessionEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoOfflineUserSessionEntity.java
new file mode 100644
index 0000000000..dd59869e47
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoOfflineUserSessionEntity.java
@@ -0,0 +1,10 @@
+package org.keycloak.models.mongo.keycloak.entities;
+
+import org.keycloak.connections.mongo.api.MongoCollection;
+
+/**
+ * @author Marek Posolda
+ */
+@MongoCollection(collectionName = "offlineUserSessions")
+public class MongoOfflineUserSessionEntity extends MongoUserSessionEntity {
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoOnlineUserSessionEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoOnlineUserSessionEntity.java
new file mode 100644
index 0000000000..7df63a97e3
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoOnlineUserSessionEntity.java
@@ -0,0 +1,10 @@
+package org.keycloak.models.mongo.keycloak.entities;
+
+import org.keycloak.connections.mongo.api.MongoCollection;
+
+/**
+ * @author Marek Posolda
+ */
+@MongoCollection(collectionName = "userSessions")
+public class MongoOnlineUserSessionEntity extends MongoUserSessionEntity {
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUserSessionEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUserSessionEntity.java
new file mode 100644
index 0000000000..1da6d310dd
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUserSessionEntity.java
@@ -0,0 +1,15 @@
+package org.keycloak.models.mongo.keycloak.entities;
+
+import org.keycloak.connections.mongo.api.MongoIdentifiableEntity;
+import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
+import org.keycloak.models.entities.PersistentUserSessionEntity;
+
+/**
+ * @author Marek Posolda
+ */
+public abstract class MongoUserSessionEntity extends PersistentUserSessionEntity implements MongoIdentifiableEntity {
+
+ @Override
+ public void afterRemove(MongoStoreInvocationContext invocationContext) {
+ }
+}
diff --git a/model/mongo/src/main/resources/META-INF/services/org.keycloak.models.session.UserSessionPersisterProviderFactory b/model/mongo/src/main/resources/META-INF/services/org.keycloak.models.session.UserSessionPersisterProviderFactory
new file mode 100644
index 0000000000..b8acb44672
--- /dev/null
+++ b/model/mongo/src/main/resources/META-INF/services/org.keycloak.models.session.UserSessionPersisterProviderFactory
@@ -0,0 +1 @@
+org.keycloak.models.mongo.keycloak.adapters.MongoUserSessionPersisterProviderFactory
\ No newline at end of file
diff --git a/model/sessions-infinispan/pom.xml b/model/sessions-infinispan/pom.xml
index ee64a6be0f..a9cdf35bfe 100755
--- a/model/sessions-infinispan/pom.xml
+++ b/model/sessions-infinispan/pom.xml
@@ -30,5 +30,10 @@
org.infinispan
infinispan-core
+
+ junit
+ junit
+ test
+
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java
index 179a1f0211..a0b653199c 100755
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java
@@ -26,13 +26,16 @@ public class ClientSessionAdapter implements ClientSessionModel {
private Cache cache;
private RealmModel realm;
private ClientSessionEntity entity;
+ private boolean offline;
- public ClientSessionAdapter(KeycloakSession session, InfinispanUserSessionProvider provider, Cache cache, RealmModel realm, ClientSessionEntity entity) {
+ public ClientSessionAdapter(KeycloakSession session, InfinispanUserSessionProvider provider, Cache cache, RealmModel realm,
+ ClientSessionEntity entity, boolean offline) {
this.session = session;
this.provider = provider;
this.cache = cache;
this.realm = realm;
this.entity = entity;
+ this.offline = offline;
}
@Override
@@ -51,8 +54,8 @@ public class ClientSessionAdapter implements ClientSessionModel {
}
@Override
- public UserSessionModel getUserSession() {
- return entity.getUserSession() != null ? provider.getUserSession(realm, entity.getUserSession()) : null;
+ public UserSessionAdapter getUserSession() {
+ return entity.getUserSession() != null ? provider.getUserSession(realm, entity.getUserSession(), offline) : null;
}
@Override
@@ -63,14 +66,15 @@ public class ClientSessionAdapter implements ClientSessionModel {
}
entity.setUserSession(null);
} else {
+ UserSessionAdapter userSessionAdapter = (UserSessionAdapter) userSession;
if (entity.getUserSession() != null) {
if (entity.getUserSession().equals(userSession.getId())) {
return;
} else {
- provider.dettachSession(userSession, this);
+ provider.dettachSession(userSessionAdapter, this);
}
} else {
- provider.attachSession(userSession, this);
+ provider.attachSession(userSessionAdapter, this);
}
entity.setUserSession(userSession.getId());
@@ -113,7 +117,8 @@ public class ClientSessionAdapter implements ClientSessionModel {
@Override
public Set getRoles() {
- return entity.getRoles();
+ if (entity.getRoles() == null || entity.getRoles().isEmpty()) return Collections.emptySet();
+ return new HashSet<>(entity.getRoles());
}
@Override
@@ -124,7 +129,8 @@ public class ClientSessionAdapter implements ClientSessionModel {
@Override
public Set getProtocolMappers() {
- return entity.getProtocolMappers();
+ if (entity.getProtocolMappers() == null || entity.getProtocolMappers().isEmpty()) return Collections.emptySet();
+ return new HashSet<>(entity.getProtocolMappers());
}
@Override
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
index 83776df17a..dbfecb4357 100755
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
@@ -7,6 +7,7 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakTransaction;
+import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
@@ -18,6 +19,7 @@ import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
import org.keycloak.models.sessions.infinispan.mapreduce.ClientSessionMapper;
+import org.keycloak.models.sessions.infinispan.mapreduce.ClientSessionsOfUserSessionMapper;
import org.keycloak.models.sessions.infinispan.mapreduce.FirstResultReducer;
import org.keycloak.models.sessions.infinispan.mapreduce.LargestResultReducer;
import org.keycloak.models.sessions.infinispan.mapreduce.SessionMapper;
@@ -36,6 +38,7 @@ import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* @author Stian Thorgersen
@@ -46,18 +49,25 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
private final KeycloakSession session;
private final Cache sessionCache;
+ private final Cache offlineSessionCache;
private final Cache loginFailureCache;
private final InfinispanKeycloakTransaction tx;
- public InfinispanUserSessionProvider(KeycloakSession session, Cache sessionCache, Cache loginFailureCache) {
+ public InfinispanUserSessionProvider(KeycloakSession session, Cache sessionCache, Cache offlineSessionCache,
+ Cache loginFailureCache) {
this.session = session;
this.sessionCache = sessionCache;
+ this.offlineSessionCache = offlineSessionCache;
this.loginFailureCache = loginFailureCache;
this.tx = new InfinispanKeycloakTransaction();
session.getTransaction().enlistAfterCompletion(tx);
}
+ protected Cache getCache(boolean offline) {
+ return offline ? offlineSessionCache : sessionCache;
+ }
+
@Override
public ClientSessionModel createClientSession(RealmModel realm, ClientModel client) {
String id = KeycloakModelUtils.generateId();
@@ -70,7 +80,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
tx.put(sessionCache, id, entity);
- return wrap(realm, entity);
+ return wrap(realm, entity, false);
}
@Override
@@ -95,29 +105,57 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
tx.put(sessionCache, id, entity);
- return wrap(realm, entity);
+ return wrap(realm, entity, false);
}
@Override
public ClientSessionModel getClientSession(RealmModel realm, String id) {
- ClientSessionEntity entity = (ClientSessionEntity) sessionCache.get(id);
- return wrap(realm, entity);
+ return getClientSession(realm, id, false);
+ }
+
+ protected ClientSessionModel getClientSession(RealmModel realm, String id, boolean offline) {
+ Cache cache = getCache(offline);
+ ClientSessionEntity entity = (ClientSessionEntity) cache.get(id);
+
+ // Chance created in this transaction
+ if (entity == null) {
+ entity = (ClientSessionEntity) tx.get(cache, id);
+ }
+
+ return wrap(realm, entity, offline);
}
@Override
public ClientSessionModel getClientSession(String id) {
ClientSessionEntity entity = (ClientSessionEntity) sessionCache.get(id);
+
+ // Chance created in this transaction
+ if (entity == null) {
+ entity = (ClientSessionEntity) tx.get(sessionCache, id);
+ }
+
if (entity != null) {
RealmModel realm = session.realms().getRealm(entity.getRealm());
- return wrap(realm, entity);
+ return wrap(realm, entity, false);
}
return null;
}
@Override
public UserSessionModel getUserSession(RealmModel realm, String id) {
- UserSessionEntity entity = (UserSessionEntity) sessionCache.get(id);
- return wrap(realm, entity);
+ return getUserSession(realm, id, false);
+ }
+
+ protected UserSessionAdapter getUserSession(RealmModel realm, String id, boolean offline) {
+ Cache cache = getCache(offline);
+ UserSessionEntity entity = (UserSessionEntity) cache.get(id);
+
+ // Chance created in this transaction
+ if (entity == null) {
+ entity = (UserSessionEntity) tx.get(cache, id);
+ }
+
+ return wrap(realm, entity, offline);
}
@Override
@@ -127,7 +165,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
.reducedWith(new FirstResultReducer())
.execute();
- return wrapUserSessions(realm, sessions.values());
+ return wrapUserSessions(realm, sessions.values(), false);
}
@Override
@@ -137,7 +175,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
.reducedWith(new FirstResultReducer())
.execute();
- return wrapUserSessions(realm, sessions.values());
+ return wrapUserSessions(realm, sessions.values(), false);
}
@Override
@@ -147,7 +185,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
.reducedWith(new FirstResultReducer())
.execute();
- List userSessionModels = wrapUserSessions(realm, sessions.values());
+ List userSessionModels = wrapUserSessions(realm, sessions.values(), false);
if (userSessionModels.isEmpty()) return null;
return userSessionModels.get(0);
}
@@ -159,7 +197,13 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
@Override
public List getUserSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
- Map map = new MapReduceTask(sessionCache)
+ return getUserSessions(realm, client, firstResult, maxResults, false);
+ }
+
+ protected List getUserSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults, boolean offline) {
+ Cache cache = getCache(offline);
+
+ Map map = new MapReduceTask(cache)
.mappedWith(ClientSessionMapper.create(realm.getId()).client(client.getId()).emitUserSessionAndTimestamp())
.reducedWith(new LargestResultReducer())
.execute();
@@ -192,9 +236,9 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
List userSessions = new LinkedList();
for (Map.Entry e : sessionTimestamps) {
- UserSessionEntity userSessionEntity = (UserSessionEntity) sessionCache.get(e.getKey());
+ UserSessionEntity userSessionEntity = (UserSessionEntity) cache.get(e.getKey());
if (userSessionEntity != null) {
- userSessions.add(wrap(realm, userSessionEntity));
+ userSessions.add(wrap(realm, userSessionEntity, offline));
}
}
@@ -214,13 +258,19 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
.reducedWith(new FirstResultReducer())
.execute();
- return wrapUserSessions(realm, sessions.values());
+ return wrapUserSessions(realm, sessions.values(), false);
}
@Override
public int getActiveUserSessions(RealmModel realm, ClientModel client) {
- Map map = new MapReduceTask(sessionCache)
+ return getUserSessionsCount(realm, client, false);
+ }
+
+ protected int getUserSessionsCount(RealmModel realm, ClientModel client, boolean offline) {
+ Cache cache = getCache(offline);
+
+ Map map = new MapReduceTask(cache)
.mappedWith(ClientSessionMapper.create(realm.getId()).client(client.getId()).emitUserSessionAndTimestamp())
.reducedWith(new LargestResultReducer()).execute();
@@ -234,13 +284,19 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
@Override
public void removeUserSessions(RealmModel realm, UserModel user) {
- Map sessions = new MapReduceTask(sessionCache)
+ removeUserSessions(realm, user, false);
+ }
+
+ protected void removeUserSessions(RealmModel realm, UserModel user, boolean offline) {
+ Cache cache = getCache(offline);
+
+ Map sessions = new MapReduceTask(cache)
.mappedWith(UserSessionMapper.create(realm.getId()).user(user.getId()).emitKey())
.reducedWith(new FirstResultReducer())
.execute();
for (String id : sessions.keySet()) {
- removeUserSession(realm, id);
+ removeUserSession(realm, id, offline);
}
}
@@ -271,13 +327,19 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
@Override
public void removeUserSessions(RealmModel realm) {
- Map ids = new MapReduceTask(sessionCache)
+ removeUserSessions(realm, false);
+ }
+
+ protected void removeUserSessions(RealmModel realm, boolean offline) {
+ Cache cache = getCache(offline);
+
+ Map ids = new MapReduceTask(cache)
.mappedWith(SessionMapper.create(realm.getId()).emitKey())
.reducedWith(new FirstResultReducer())
.execute();
for (String id : ids.keySet()) {
- sessionCache.remove(id);
+ cache.remove(id);
}
}
@@ -319,25 +381,39 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
@Override
public void onRealmRemoved(RealmModel realm) {
- removeUserSessions(realm);
+ removeUserSessions(realm, true);
+ removeUserSessions(realm, false);
removeAllUserLoginFailures(realm);
}
@Override
public void onClientRemoved(RealmModel realm, ClientModel client) {
- Map map = new MapReduceTask(sessionCache)
- .mappedWith(ClientSessionMapper.create(realm.getId()).client(client.getId()).emitKey())
+ onClientRemoved(realm, client, true);
+ onClientRemoved(realm, client, false);
+ }
+
+ private void onClientRemoved(RealmModel realm, ClientModel client, boolean offline) {
+ Cache cache = getCache(offline);
+
+ Map map = new MapReduceTask(cache)
+ .mappedWith(ClientSessionMapper.create(realm.getId()).client(client.getId()))
.reducedWith(new FirstResultReducer())
.execute();
- for (String id : map.keySet()) {
- tx.remove(sessionCache, id);
+ for (Map.Entry entry : map.entrySet()) {
+
+ // detach from userSession
+ ClientSessionAdapter adapter = wrap(realm, entry.getValue(), offline);
+ adapter.setUserSession(null);
+
+ tx.remove(cache, entry.getKey());
}
}
@Override
public void onUserRemoved(RealmModel realm, UserModel user) {
- removeUserSessions(realm, user);
+ removeUserSessions(realm, user, true);
+ removeUserSessions(realm, user, false);
loginFailureCache.remove(new LoginFailureKey(realm.getId(), user.getUsername()));
loginFailureCache.remove(new LoginFailureKey(realm.getId(), user.getEmail()));
@@ -347,20 +423,26 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
public void close() {
}
- void attachSession(UserSessionModel userSession, ClientSessionModel clientSession) {
- UserSessionEntity entity = ((UserSessionAdapter) userSession).getEntity();
+ void attachSession(UserSessionAdapter userSession, ClientSessionModel clientSession) {
+ UserSessionEntity entity = userSession.getEntity();
String clientSessionId = clientSession.getId();
if (entity.getClientSessions() == null) {
entity.setClientSessions(new HashSet());
}
if (!entity.getClientSessions().contains(clientSessionId)) {
entity.getClientSessions().add(clientSessionId);
- tx.replace(sessionCache, entity.getId(), entity);
+ userSession.update();
}
}
@Override
public void removeClientSession(RealmModel realm, ClientSessionModel clientSession) {
+ removeClientSession(realm, clientSession, false);
+ }
+
+ protected void removeClientSession(RealmModel realm, ClientSessionModel clientSession, boolean offline) {
+ Cache cache = getCache(offline);
+
UserSessionModel userSession = clientSession.getUserSession();
if (userSession != null) {
UserSessionEntity entity = ((UserSessionAdapter) userSession).getEntity();
@@ -368,34 +450,40 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
entity.getClientSessions().remove(clientSession.getId());
}
- tx.replace(sessionCache, entity.getId(), entity);
+ tx.replace(cache, entity.getId(), entity);
}
- tx.remove(sessionCache, clientSession.getId());
+ tx.remove(cache, clientSession.getId());
}
- void dettachSession(UserSessionModel userSession, ClientSessionModel clientSession) {
- UserSessionEntity entity = ((UserSessionAdapter) userSession).getEntity();
+ void dettachSession(UserSessionAdapter userSession, ClientSessionModel clientSession) {
+ UserSessionEntity entity = userSession.getEntity();
String clientSessionId = clientSession.getId();
if (entity.getClientSessions() != null && entity.getClientSessions().contains(clientSessionId)) {
entity.getClientSessions().remove(clientSessionId);
if (entity.getClientSessions().isEmpty()) {
entity.setClientSessions(null);
}
- tx.replace(sessionCache, entity.getId(), entity);
+ userSession.update();
}
}
protected void removeUserSession(RealmModel realm, String userSessionId) {
- tx.remove(sessionCache, userSessionId);
+ removeUserSession(realm, userSessionId, false);
+ }
- Map map = new MapReduceTask(sessionCache)
+ protected void removeUserSession(RealmModel realm, String userSessionId, boolean offline) {
+ Cache cache = getCache(offline);
+
+ tx.remove(cache, userSessionId);
+
+ Map map = new MapReduceTask(cache)
.mappedWith(ClientSessionMapper.create(realm.getId()).userSession(userSessionId).emitKey())
.reducedWith(new FirstResultReducer())
.execute();
for (String id : map.keySet()) {
- tx.remove(sessionCache, id);
+ tx.remove(cache, id);
}
}
@@ -404,20 +492,22 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
return tx;
}
- UserSessionModel wrap(RealmModel realm, UserSessionEntity entity) {
- return entity != null ? new UserSessionAdapter(session, this, sessionCache, realm, entity) : null;
+ UserSessionAdapter wrap(RealmModel realm, UserSessionEntity entity, boolean offline) {
+ Cache cache = getCache(offline);
+ return entity != null ? new UserSessionAdapter(session, this, cache, realm, entity, offline) : null;
}
- List wrapUserSessions(RealmModel realm, Collection entities) {
+ List wrapUserSessions(RealmModel realm, Collection entities, boolean offline) {
List models = new LinkedList();
for (UserSessionEntity e : entities) {
- models.add(wrap(realm, e));
+ models.add(wrap(realm, e, offline));
}
return models;
}
- ClientSessionModel wrap(RealmModel realm, ClientSessionEntity entity) {
- return entity != null ? new ClientSessionAdapter(session, this, sessionCache, realm, entity) : null;
+ ClientSessionAdapter wrap(RealmModel realm, ClientSessionEntity entity, boolean offline) {
+ Cache cache = getCache(offline);
+ return entity != null ? new ClientSessionAdapter(session, this, cache, realm, entity, offline) : null;
}
@@ -425,14 +515,113 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
return entity != null ? new UsernameLoginFailureAdapter(this, loginFailureCache, key, entity) : null;
}
- List wrapClientSessions(RealmModel realm, Collection entities) {
+ List wrapClientSessions(RealmModel realm, Collection entities, boolean offline) {
List models = new LinkedList();
for (ClientSessionEntity e : entities) {
- models.add(wrap(realm, e));
+ models.add(wrap(realm, e, offline));
}
return models;
}
+
+ @Override
+ public UserSessionModel createOfflineUserSession(UserSessionModel userSession) {
+ UserSessionEntity entity = new UserSessionEntity();
+ entity.setId(userSession.getId());
+ entity.setRealm(userSession.getRealm().getId());
+
+ entity.setAuthMethod(userSession.getAuthMethod());
+ entity.setBrokerSessionId(userSession.getBrokerSessionId());
+ entity.setBrokerUserId(userSession.getBrokerUserId());
+ entity.setIpAddress(userSession.getIpAddress());
+ entity.setLastSessionRefresh(userSession.getLastSessionRefresh());
+ entity.setLoginUsername(userSession.getLoginUsername());
+ entity.setNotes(userSession.getNotes());
+ entity.setRememberMe(userSession.isRememberMe());
+ entity.setStarted(userSession.getStarted());
+ entity.setState(userSession.getState());
+ entity.setUser(userSession.getUser().getId());
+
+ tx.put(offlineSessionCache, userSession.getId(), entity);
+ return wrap(userSession.getRealm(), entity, true);
+ }
+
+ @Override
+ public UserSessionModel getOfflineUserSession(RealmModel realm, String userSessionId) {
+ return getUserSession(realm, userSessionId, true);
+ }
+
+ @Override
+ public void removeOfflineUserSession(RealmModel realm, String userSessionId) {
+ removeUserSession(realm, userSessionId, true);
+ }
+
+ @Override
+ public ClientSessionModel createOfflineClientSession(ClientSessionModel clientSession) {
+ ClientSessionEntity entity = new ClientSessionEntity();
+ entity.setId(clientSession.getId());
+ entity.setRealm(clientSession.getRealm().getId());
+
+ entity.setAction(clientSession.getAction());
+ entity.setAuthenticatorStatus(clientSession.getExecutionStatus());
+ entity.setAuthMethod(clientSession.getAuthMethod());
+ if (clientSession.getAuthenticatedUser() != null) {
+ entity.setAuthUserId(clientSession.getAuthenticatedUser().getId());
+ }
+ entity.setClient(clientSession.getClient().getId());
+ entity.setNotes(clientSession.getNotes());
+ entity.setProtocolMappers(clientSession.getProtocolMappers());
+ entity.setRedirectUri(clientSession.getRedirectUri());
+ entity.setRoles(clientSession.getRoles());
+ entity.setTimestamp(clientSession.getTimestamp());
+ entity.setUserSessionNotes(clientSession.getUserSessionNotes());
+
+ tx.put(offlineSessionCache, clientSession.getId(), entity);
+ return wrap(clientSession.getRealm(), entity, true);
+ }
+
+ @Override
+ public ClientSessionModel getOfflineClientSession(RealmModel realm, String clientSessionId) {
+ return getClientSession(realm, clientSessionId, true);
+ }
+
+ @Override
+ public List getOfflineClientSessions(RealmModel realm, UserModel user) {
+ Map sessions = new MapReduceTask(offlineSessionCache)
+ .mappedWith(UserSessionMapper.create(realm.getId()).user(user.getId()))
+ .reducedWith(new FirstResultReducer())
+ .execute();
+
+ List clientSessions = new LinkedList<>();
+ for (UserSessionEntity userSession : sessions.values()) {
+ Set currClientSessions = userSession.getClientSessions();
+ for (String clientSessionId : currClientSessions) {
+ ClientSessionEntity cls = (ClientSessionEntity) offlineSessionCache.get(clientSessionId);
+ if (cls != null) {
+ clientSessions.add(cls);
+ }
+ }
+ }
+
+ return wrapClientSessions(realm, clientSessions, true);
+ }
+
+ @Override
+ public void removeOfflineClientSession(RealmModel realm, String clientSessionId) {
+ ClientSessionModel clientSession = getOfflineClientSession(realm, clientSessionId);
+ removeClientSession(realm, clientSession, true);
+ }
+
+ @Override
+ public int getOfflineSessionsCount(RealmModel realm, ClientModel client) {
+ return getUserSessionsCount(realm, client, true);
+ }
+
+ @Override
+ public List getOfflineUserSessions(RealmModel realm, ClientModel client, int first, int max) {
+ return getUserSessions(realm, client, first, max, true);
+ }
+
class InfinispanKeycloakTransaction implements KeycloakTransaction {
private boolean active;
@@ -478,17 +667,19 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
public void put(Cache cache, Object key, Object value) {
log.tracev("Adding cache operation: {0} on {1}", CacheOperation.ADD, key);
- if (tasks.containsKey(key)) {
+ Object taskKey = getTaskKey(cache, key);
+ if (tasks.containsKey(taskKey)) {
throw new IllegalStateException("Can't add session: task in progress for session");
} else {
- tasks.put(key, new CacheTask(cache, CacheOperation.ADD, key, value));
+ tasks.put(taskKey, new CacheTask(cache, CacheOperation.ADD, key, value));
}
}
public void replace(Cache cache, Object key, Object value) {
log.tracev("Adding cache operation: {0} on {1}", CacheOperation.REPLACE, key);
- CacheTask current = tasks.get(key);
+ Object taskKey = getTaskKey(cache, key);
+ CacheTask current = tasks.get(taskKey);
if (current != null) {
switch (current.operation) {
case ADD:
@@ -499,14 +690,40 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
return;
}
} else {
- tasks.put(key, new CacheTask(cache, CacheOperation.REPLACE, key, value));
+ tasks.put(taskKey, new CacheTask(cache, CacheOperation.REPLACE, key, value));
}
}
public void remove(Cache cache, Object key) {
log.tracev("Adding cache operation: {0} on {1}", CacheOperation.REMOVE, key);
- tasks.put(key, new CacheTask(cache, CacheOperation.REMOVE, key, null));
+ Object taskKey = getTaskKey(cache, key);
+ tasks.put(taskKey, new CacheTask(cache, CacheOperation.REMOVE, key, null));
+ }
+
+ // This is for possibility to lookup for session by id, which was created in this transaction
+ public Object get(Cache cache, Object key) {
+ Object taskKey = getTaskKey(cache, key);
+ CacheTask current = tasks.get(taskKey);
+ if (current != null) {
+ switch (current.operation) {
+ case ADD:
+ case REPLACE:
+ return current.value; }
+ }
+
+ return null;
+ }
+
+ private Object getTaskKey(Cache cache, Object key) {
+ if (key instanceof String) {
+ return new StringBuilder(cache.getName())
+ .append("::")
+ .append(key.toString()).toString();
+ } else {
+ // loginFailure cache
+ return key;
+ }
}
public class CacheTask {
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 ec6025b2f8..c88e4901f8 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
@@ -7,12 +7,18 @@ import org.keycloak.Config;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.KeycloakSessionTask;
import org.keycloak.models.UserSessionProvider;
import org.keycloak.models.UserSessionProviderFactory;
+import org.keycloak.models.session.UserSessionPersisterProvider;
import org.keycloak.models.sessions.infinispan.compat.MemUserSessionProviderFactory;
+import org.keycloak.models.sessions.infinispan.compat.SimpleUserSessionInitializer;
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
+import org.keycloak.models.sessions.infinispan.initializer.InfinispanUserSessionInitializer;
+import org.keycloak.models.sessions.infinispan.initializer.OfflineUserSessionLoader;
+import org.keycloak.models.utils.KeycloakModelUtils;
/**
* 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
@@ -24,28 +30,19 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
private static final Logger log = Logger.getLogger(InfinispanUserSessionProviderFactory.class);
+ private Config.Scope config;
private Boolean compatMode;
private MemUserSessionProviderFactory compatProviderFactory;
@Override
public UserSessionProvider create(KeycloakSession session) {
- 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 offlineSessionsCache = connections.getCache(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME);
Cache loginFailures = connections.getCache(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME);
- return new InfinispanUserSessionProvider(session, cache, loginFailures);
+ return new InfinispanUserSessionProvider(session, cache, offlineSessionsCache, loginFailures);
} else {
return compatProviderFactory.create(session);
}
@@ -53,11 +50,67 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
@Override
public void init(Config.Scope config) {
+ this.config = config;
}
@Override
- public void postInit(KeycloakSessionFactory factory) {
+ public void postInit(final KeycloakSessionFactory factory) {
+ KeycloakModelUtils.runJobInTransaction(factory, new KeycloakSessionTask() {
+ @Override
+ public void run(KeycloakSession session) {
+ compatMode = isCompatMode(session);
+ if (compatMode) {
+ compatProviderFactory = new MemUserSessionProviderFactory();
+ }
+
+ log.debug("Clearing detached sessions from persistent storage");
+ UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
+ if (persister == null) {
+ throw new RuntimeException("userSessionPersister not configured. Please see the migration docs and upgrade your configuration");
+ } else {
+ persister.clearDetachedUserSessions();
+ }
+ }
+
+ });
+
+ // Max count of worker errors. Initialization will end with exception when this number is reached
+ int maxErrors = config.getInt("maxErrors", 50);
+
+ // Count of sessions to be computed in each segment
+ int sessionsPerSegment = config.getInt("sessionsPerSegment", 100);
+
+ // TODO: Possibility to run this asynchronously to not block start time
+ loadPersistentSessions(factory, maxErrors, sessionsPerSegment);
+ }
+
+
+ @Override
+ public void loadPersistentSessions(final KeycloakSessionFactory sessionFactory, final int maxErrors, final int sessionsPerSegment) {
+ log.debug("Start pre-loading userSessions and clientSessions from persistent storage");
+
+ if (compatMode) {
+ SimpleUserSessionInitializer initializer = new SimpleUserSessionInitializer(sessionFactory, new OfflineUserSessionLoader(), sessionsPerSegment);
+ initializer.loadPersistentSessions();
+
+ } else {
+ KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
+
+ @Override
+ public void run(KeycloakSession session) {
+ InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
+ Cache cache = connections.getCache(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME);
+
+ InfinispanUserSessionInitializer initializer = new InfinispanUserSessionInitializer(sessionFactory, cache, new OfflineUserSessionLoader(), maxErrors, sessionsPerSegment, "offlineUserSessions");
+ initializer.initCache();
+ initializer.loadPersistentSessions();
+ }
+
+ });
+ }
+
+ log.debug("Pre-loading userSessions and clientSessions from persistent storage finished");
}
@Override
@@ -72,11 +125,18 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
return "infinispan";
}
- private static boolean isCompatMode(KeycloakSession session) {
+ private boolean isCompatMode(KeycloakSession session) {
+ // For unit tests
+ if (this.config.getBoolean("enforceCompat", false)) {
+ log.info("Enforced compatibility mode for infinispan. Falling back to deprecated mem user session provider.");
+ return true;
+ }
+
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) {
+ log.info("Infinispan version doesn't support map reduce for local cache. Falling back to deprecated mem user session provider.");
return true;
}
}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java
index c7104fbfe2..1601acb798 100755
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java
@@ -31,18 +31,27 @@ public class UserSessionAdapter implements UserSessionModel {
private final UserSessionEntity entity;
- public UserSessionAdapter(KeycloakSession session, InfinispanUserSessionProvider provider, Cache cache, RealmModel realm, UserSessionEntity entity) {
+ private final boolean offline;
+
+ public UserSessionAdapter(KeycloakSession session, InfinispanUserSessionProvider provider, Cache cache, RealmModel realm,
+ UserSessionEntity entity, boolean offline) {
this.session = session;
this.provider = provider;
this.cache = cache;
this.realm = realm;
this.entity = entity;
+ this.offline = offline;
}
public String getId() {
return entity.getId();
}
+ @Override
+ public RealmModel getRealm() {
+ return realm;
+ }
+
@Override
public String getBrokerSessionId() {
return entity.getBrokerSessionId();
@@ -129,14 +138,14 @@ public class UserSessionAdapter implements UserSessionModel {
@Override
public List getClientSessions() {
if (entity.getClientSessions() != null) {
- List clientSessions = new LinkedList();
+ List clientSessions = new LinkedList<>();
for (String c : entity.getClientSessions()) {
- ClientSessionEntity clientSession = (ClientSessionEntity) cache.get(c);
+ ClientSessionModel clientSession = provider.getClientSession(realm, c, offline);
if (clientSession != null) {
clientSessions.add(clientSession);
}
}
- return provider.wrapClientSessions(realm, clientSessions);
+ return clientSessions;
} else {
return Collections.emptyList();
}
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
index 947c605663..0e5f2b9784 100755
--- 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
@@ -4,6 +4,7 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
@@ -38,13 +39,21 @@ public class MemUserSessionProvider implements UserSessionProvider {
private final ConcurrentHashMap clientSessions;
private final ConcurrentHashMap loginFailures;
- public MemUserSessionProvider(KeycloakSession session, ConcurrentHashMap userSessions, ConcurrentHashMap userSessionsByBrokerSessionId, ConcurrentHashMap> userSessionsByBrokerUserId, ConcurrentHashMap clientSessions, ConcurrentHashMap loginFailures) {
+ private final ConcurrentHashMap offlineUserSessions;
+ private final ConcurrentHashMap offlineClientSessions;
+
+ public MemUserSessionProvider(KeycloakSession session, ConcurrentHashMap userSessions, ConcurrentHashMap userSessionsByBrokerSessionId,
+ ConcurrentHashMap> userSessionsByBrokerUserId, ConcurrentHashMap clientSessions,
+ ConcurrentHashMap loginFailures,
+ ConcurrentHashMap offlineUserSessions, ConcurrentHashMap offlineClientSessions) {
this.session = session;
this.userSessions = userSessions;
this.clientSessions = clientSessions;
this.loginFailures = loginFailures;
this.userSessionsByBrokerSessionId = userSessionsByBrokerSessionId;
this.userSessionsByBrokerUserId = userSessionsByBrokerUserId;
+ this.offlineUserSessions = offlineUserSessions;
+ this.offlineClientSessions = offlineClientSessions;
}
@Override
@@ -189,6 +198,12 @@ public class MemUserSessionProvider implements UserSessionProvider {
@Override
public List getUserSessions(RealmModel realm, ClientModel client) {
+ return getUserSessions(realm, client, false);
+ }
+
+ protected List getUserSessions(RealmModel realm, ClientModel client, boolean offline) {
+ ConcurrentHashMap clientSessions = offline ? this.offlineClientSessions : this.clientSessions;
+
List userSessionEntities = new LinkedList();
for (ClientSessionEntity s : clientSessions.values()) {
String realmId = realm.getId();
@@ -210,7 +225,11 @@ public class MemUserSessionProvider implements UserSessionProvider {
@Override
public List getUserSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
- List userSessions = getUserSessions(realm, client);
+ return getUserSessions(realm, client, firstResult, maxResults, false);
+ }
+
+ protected List getUserSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults, boolean offline) {
+ List userSessions = getUserSessions(realm, client, offline);
if (firstResult > userSessions.size()) {
return Collections.emptyList();
}
@@ -221,7 +240,7 @@ public class MemUserSessionProvider implements UserSessionProvider {
@Override
public int getActiveUserSessions(RealmModel realm, ClientModel client) {
- return getUserSessions(realm, client).size();
+ return getUserSessions(realm, client, false).size();
}
@Override
@@ -229,40 +248,51 @@ public class MemUserSessionProvider implements UserSessionProvider {
UserSessionEntity entity = getUserSessionEntity(realm, session.getId());
if (entity != null) {
userSessions.remove(entity.getId());
- remove(entity);
+ remove(entity, false);
}
}
@Override
public void removeUserSessions(RealmModel realm, UserModel user) {
- Iterator itr = userSessions.values().iterator();
+ removeUserSessions(realm, user, false);
+ }
+
+ protected void removeUserSessions(RealmModel realm, UserModel user, boolean offline) {
+ Iterator itr = offline ? offlineUserSessions.values().iterator() : 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);
+ remove(s, offline);
}
}
}
- 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());
+ protected void remove(UserSessionEntity s, boolean offline) {
+ if (offline) {
+ for (ClientSessionEntity clientSession : s.getClientSessions()) {
+ offlineClientSessions.remove(clientSession.getId());
+ }
+ } else {
+ 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());
+ }
}
- for (ClientSessionEntity clientSession : s.getClientSessions()) {
- clientSessions.remove(clientSession.getId());
- }
}
@Override
@@ -273,7 +303,7 @@ public class MemUserSessionProvider implements UserSessionProvider {
if (s.getRealm().equals(realm.getId()) && (s.getLastSessionRefresh() < Time.currentTime() - realm.getSsoSessionIdleTimeout() || s.getStarted() < Time.currentTime() - realm.getSsoSessionMaxLifespan())) {
itr.remove();
- remove(s);
+ remove(s, false);
}
}
int expired = Time.currentTime() - RealmInfoUtil.getDettachedClientSessionLifespan(realm);
@@ -288,16 +318,19 @@ public class MemUserSessionProvider implements UserSessionProvider {
@Override
public void removeUserSessions(RealmModel realm) {
- Iterator itr = userSessions.values().iterator();
+ removeUserSessions(realm, false);
+ }
+
+ protected void removeUserSessions(RealmModel realm, boolean offline) {
+ Iterator itr = offline ? offlineUserSessions.values().iterator() : userSessions.values().iterator();
while (itr.hasNext()) {
UserSessionEntity s = itr.next();
if (s.getRealm().equals(realm.getId())) {
itr.remove();
-
- remove(s);
+ remove(s, offline);
}
}
- Iterator citr = clientSessions.values().iterator();
+ Iterator citr = offline ? offlineClientSessions.values().iterator() : clientSessions.values().iterator();
while (citr.hasNext()) {
ClientSessionEntity c = citr.next();
if (c.getSession() == null && c.getRealmId().equals(realm.getId())) {
@@ -340,15 +373,23 @@ public class MemUserSessionProvider implements UserSessionProvider {
@Override
public void onRealmRemoved(RealmModel realm) {
- removeUserSessions(realm);
+ removeUserSessions(realm, true);
+ removeUserSessions(realm, false);
removeAllUserLoginFailures(realm);
}
@Override
public void onClientRemoved(RealmModel realm, ClientModel client) {
- for (ClientSessionEntity e : clientSessions.values()) {
+ onClientRemoved(realm, client, true);
+ onClientRemoved(realm, client, false);
+ }
+
+ private void onClientRemoved(RealmModel realm, ClientModel client, boolean offline) {
+ ConcurrentHashMap clientSessionsMap = offline ? offlineClientSessions : clientSessions;
+
+ for (ClientSessionEntity e : clientSessionsMap.values()) {
if (e.getRealmId().equals(realm.getId()) && e.getClientId().equals(client.getId())) {
- clientSessions.remove(e.getId());
+ clientSessionsMap.remove(e.getId());
e.getSession().removeClientSession(e);
}
}
@@ -356,12 +397,130 @@ public class MemUserSessionProvider implements UserSessionProvider {
@Override
public void onUserRemoved(RealmModel realm, UserModel user) {
- removeUserSessions(realm, user);
+ removeUserSessions(realm, user, true);
+ removeUserSessions(realm, user, false);
loginFailures.remove(new UsernameLoginFailureKey(realm.getId(), user.getUsername()));
loginFailures.remove(new UsernameLoginFailureKey(realm.getId(), user.getEmail()));
}
+
+ @Override
+ public UserSessionModel createOfflineUserSession(UserSessionModel userSession) {
+ UserSessionEntity entity = new UserSessionEntity();
+ entity.setId(userSession.getId());
+ entity.setRealm(userSession.getRealm().getId());
+
+ entity.setAuthMethod(userSession.getAuthMethod());
+ entity.setBrokerSessionId(userSession.getBrokerSessionId());
+ entity.setBrokerUserId(userSession.getBrokerUserId());
+ entity.setIpAddress(userSession.getIpAddress());
+ entity.setLastSessionRefresh(userSession.getLastSessionRefresh());
+ entity.setLoginUsername(userSession.getLoginUsername());
+ if (userSession.getNotes() != null) {
+ entity.getNotes().putAll(userSession.getNotes());
+ }
+ entity.setRememberMe(userSession.isRememberMe());
+ entity.setStarted(userSession.getStarted());
+ entity.setState(userSession.getState());
+ entity.setUser(userSession.getUser().getId());
+
+ offlineUserSessions.put(userSession.getId(), entity);
+ return new UserSessionAdapter(session, this, userSession.getRealm(), entity);
+ }
+
+ @Override
+ public UserSessionModel getOfflineUserSession(RealmModel realm, String userSessionId) {
+ UserSessionEntity entity = offlineUserSessions.get(userSessionId);
+ if (entity != null && entity.getRealm().equals(realm.getId())) {
+ return new UserSessionAdapter(session, this, realm, entity);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public void removeOfflineUserSession(RealmModel realm, String userSessionId) {
+ UserSessionEntity entity = offlineUserSessions.get(userSessionId);
+ if (entity != null && entity.getRealm().equals(realm.getId())) {
+ offlineUserSessions.remove(entity);
+ remove(entity, true);
+ }
+ }
+
+ @Override
+ public ClientSessionModel createOfflineClientSession(ClientSessionModel clientSession) {
+ ClientSessionEntity entity = new ClientSessionEntity();
+ entity.setId(clientSession.getId());
+ entity.setRealmId(clientSession.getRealm().getId());
+
+ entity.setAction(clientSession.getAction());
+ entity.setAuthenticatorStatus(clientSession.getExecutionStatus());
+ entity.setAuthMethod(clientSession.getAuthMethod());
+ if (clientSession.getAuthenticatedUser() != null) {
+ entity.setAuthUserId(clientSession.getAuthenticatedUser().getId());
+ }
+ entity.setClientId(clientSession.getClient().getId());
+ if (clientSession.getNotes() != null) {
+ entity.getNotes().putAll(clientSession.getNotes());
+ }
+ entity.setProtocolMappers(clientSession.getProtocolMappers());
+ entity.setRedirectUri(clientSession.getRedirectUri());
+ entity.setRoles(clientSession.getRoles());
+ entity.setTimestamp(clientSession.getTimestamp());
+
+ if (clientSession.getUserSessionNotes() != null) {
+ entity.getUserSessionNotes().putAll(clientSession.getUserSessionNotes());
+ }
+
+ offlineClientSessions.put(clientSession.getId(), entity);
+ return new ClientSessionAdapter(session, this, clientSession.getRealm(), entity);
+ }
+
+ @Override
+ public ClientSessionModel getOfflineClientSession(RealmModel realm, String clientSessionId) {
+ ClientSessionEntity entity = offlineClientSessions.get(clientSessionId);
+ if (entity != null && entity.getRealmId().equals(realm.getId())) {
+ return new ClientSessionAdapter(session, this, realm, entity);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public List getOfflineClientSessions(RealmModel realm, UserModel user) {
+ List clientSessions = new LinkedList<>();
+ for (UserSessionEntity s : this.offlineUserSessions.values()) {
+ if (s.getRealm().equals(realm.getId()) && s.getUser().equals(user.getId())) {
+ for (ClientSessionEntity cls : s.getClientSessions()) {
+ ClientSessionAdapter clAdapter = new ClientSessionAdapter(session, this, realm, cls);
+ clientSessions.add(clAdapter);
+ }
+ }
+ }
+ return clientSessions;
+ }
+
+ @Override
+ public void removeOfflineClientSession(RealmModel realm, String clientSessionId) {
+ ClientSessionEntity entity = offlineClientSessions.get(clientSessionId);
+ if (entity != null && entity.getRealmId().equals(realm.getId())) {
+ offlineClientSessions.remove(entity.getId());
+ UserSessionEntity userSession = entity.getSession();
+ userSession.removeClientSession(entity);
+ }
+ }
+
+ @Override
+ public int getOfflineSessionsCount(RealmModel realm, ClientModel client) {
+ return getUserSessions(realm, client, true).size();
+ }
+
+ @Override
+ public List getOfflineUserSessions(RealmModel realm, ClientModel client, int first, int max) {
+ return getUserSessions(realm, client, first, max, true);
+ }
+
@Override
public void close() {
}
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
index 6a84b1a6e5..187a33ffbe 100644
--- 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
@@ -26,8 +26,12 @@ public class MemUserSessionProviderFactory {
private final ConcurrentHashMap userSessionsByBrokerSessionId = new ConcurrentHashMap<>();
private final ConcurrentHashMap> userSessionsByBrokerUserId = new ConcurrentHashMap<>();
+ private ConcurrentHashMap offlineUserSessions = new ConcurrentHashMap();
+ private ConcurrentHashMap offlineClientSessions = new ConcurrentHashMap