Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
aadd63e2b0
122 files changed
+4818
-2265
No files matched your search
@@ -27,5 +27,6 @@
|
|||||||
<artifactId>infinispan-core</artifactId>
|
<artifactId>infinispan-core</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
+13
@@ -66,6 +66,19 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
|||||||
} else {
|
} else {
|
||||||
initEmbedded();
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -11,6 +11,7 @@ public interface InfinispanConnectionProvider extends Provider {
|
|||||||
static final String REALM_CACHE_NAME = "realms";
|
static final String REALM_CACHE_NAME = "realms";
|
||||||
static final String USER_CACHE_NAME = "users";
|
static final String USER_CACHE_NAME = "users";
|
||||||
static final String SESSION_CACHE_NAME = "sessions";
|
static final String SESSION_CACHE_NAME = "sessions";
|
||||||
|
static final String OFFLINE_SESSION_CACHE_NAME = "offlineSessions";
|
||||||
static final String LOGIN_FAILURE_CACHE_NAME = "loginFailures";
|
static final String LOGIN_FAILURE_CACHE_NAME = "loginFailures";
|
||||||
|
|
||||||
<K, V> Cache<K, V> getCache(String name);
|
<K, V> Cache<K, V> getCache(String name);
|
||||||
|
|||||||
@@ -13,19 +13,23 @@
|
|||||||
</addColumn>
|
</addColumn>
|
||||||
|
|
||||||
<createTable tableName="OFFLINE_USER_SESSION">
|
<createTable tableName="OFFLINE_USER_SESSION">
|
||||||
|
<column name="USER_SESSION_ID" type="VARCHAR(36)">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
<column name="USER_ID" type="VARCHAR(36)">
|
<column name="USER_ID" type="VARCHAR(36)">
|
||||||
<constraints nullable="false"/>
|
<constraints nullable="false"/>
|
||||||
</column>
|
</column>
|
||||||
<column name="USER_SESSION_ID" type="VARCHAR(36)">
|
<column name="REALM_ID" type="VARCHAR(36)">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="LAST_SESSION_REFRESH" type="INT"/>
|
||||||
|
<column name="OFFLINE" type="BOOLEAN" defaultValueBoolean="false">
|
||||||
<constraints nullable="false"/>
|
<constraints nullable="false"/>
|
||||||
</column>
|
</column>
|
||||||
<column name="DATA" type="CLOB"/>
|
<column name="DATA" type="CLOB"/>
|
||||||
</createTable>
|
</createTable>
|
||||||
|
|
||||||
<createTable tableName="OFFLINE_CLIENT_SESSION">
|
<createTable tableName="OFFLINE_CLIENT_SESSION">
|
||||||
<column name="USER_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="CLIENT_SESSION_ID" type="VARCHAR(36)">
|
<column name="CLIENT_SESSION_ID" type="VARCHAR(36)">
|
||||||
<constraints nullable="false"/>
|
<constraints nullable="false"/>
|
||||||
</column>
|
</column>
|
||||||
@@ -35,14 +39,13 @@
|
|||||||
<column name="CLIENT_ID" type="VARCHAR(36)">
|
<column name="CLIENT_ID" type="VARCHAR(36)">
|
||||||
<constraints nullable="false"/>
|
<constraints nullable="false"/>
|
||||||
</column>
|
</column>
|
||||||
|
<column name="OFFLINE" type="BOOLEAN" defaultValueBoolean="false">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
<column name="DATA" type="CLOB"/>
|
<column name="DATA" type="CLOB"/>
|
||||||
</createTable>
|
</createTable>
|
||||||
|
|
||||||
<addPrimaryKey columnNames="USER_SESSION_ID" constraintName="CONSTRAINT_OFFLINE_US_SES_PK" tableName="OFFLINE_USER_SESSION"/>
|
<addPrimaryKey columnNames="USER_SESSION_ID, OFFLINE" constraintName="CONSTRAINT_OFFLINE_US_SES_PK" tableName="OFFLINE_USER_SESSION"/>
|
||||||
<addPrimaryKey columnNames="CLIENT_SESSION_ID" constraintName="CONSTRAINT_OFFLINE_CL_SES_PK" tableName="OFFLINE_CLIENT_SESSION"/>
|
<addPrimaryKey columnNames="CLIENT_SESSION_ID, OFFLINE" constraintName="CONSTRAINT_OFFLINE_CL_SES_PK" tableName="OFFLINE_CLIENT_SESSION"/>
|
||||||
<addForeignKeyConstraint baseColumnNames="USER_ID" baseTableName="OFFLINE_USER_SESSION" constraintName="FK_OFFLINE_US_SES_USER" referencedColumnNames="ID" referencedTableName="USER_ENTITY"/>
|
|
||||||
<addForeignKeyConstraint baseColumnNames="USER_ID" baseTableName="OFFLINE_CLIENT_SESSION" constraintName="FK_OFFLINE_CL_SES_USER" referencedColumnNames="ID" referencedTableName="USER_ENTITY"/>
|
|
||||||
<addForeignKeyConstraint baseColumnNames="USER_SESSION_ID" baseTableName="OFFLINE_CLIENT_SESSION" constraintName="FK_OFFLINE_CL_US_SES" referencedColumnNames="USER_SESSION_ID" referencedTableName="OFFLINE_USER_SESSION"/>
|
|
||||||
|
|
||||||
</changeSet>
|
</changeSet>
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
@@ -29,8 +29,8 @@
|
|||||||
<class>org.keycloak.models.jpa.entities.AuthenticationExecutionEntity</class>
|
<class>org.keycloak.models.jpa.entities.AuthenticationExecutionEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.AuthenticatorConfigEntity</class>
|
<class>org.keycloak.models.jpa.entities.AuthenticatorConfigEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.RequiredActionProviderEntity</class>
|
<class>org.keycloak.models.jpa.entities.RequiredActionProviderEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.OfflineUserSessionEntity</class>
|
<class>org.keycloak.models.jpa.session.PersistentUserSessionEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.OfflineClientSessionEntity</class>
|
<class>org.keycloak.models.jpa.session.PersistentClientSessionEntity</class>
|
||||||
|
|
||||||
<!-- JpaAuditProviders -->
|
<!-- JpaAuditProviders -->
|
||||||
<class>org.keycloak.events.jpa.EventEntity</class>
|
<class>org.keycloak.events.jpa.EventEntity</class>
|
||||||
|
|||||||
+48
-21
@@ -3,6 +3,7 @@ package org.keycloak.connections.mongo;
|
|||||||
import com.mongodb.DB;
|
import com.mongodb.DB;
|
||||||
import com.mongodb.MongoClient;
|
import com.mongodb.MongoClient;
|
||||||
import com.mongodb.MongoClientOptions;
|
import com.mongodb.MongoClientOptions;
|
||||||
|
import com.mongodb.MongoClientURI;
|
||||||
import com.mongodb.MongoCredential;
|
import com.mongodb.MongoCredential;
|
||||||
import com.mongodb.ServerAddress;
|
import com.mongodb.ServerAddress;
|
||||||
|
|
||||||
@@ -35,6 +36,8 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
|
|||||||
"org.keycloak.models.mongo.keycloak.entities.MongoClientEntity",
|
"org.keycloak.models.mongo.keycloak.entities.MongoClientEntity",
|
||||||
"org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity",
|
"org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity",
|
||||||
"org.keycloak.models.mongo.keycloak.entities.MongoMigrationModelEntity",
|
"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.IdentityProviderEntity",
|
||||||
"org.keycloak.models.entities.ClientIdentityProviderMappingEntity",
|
"org.keycloak.models.entities.ClientIdentityProviderMappingEntity",
|
||||||
"org.keycloak.models.entities.RequiredCredentialEntity",
|
"org.keycloak.models.entities.RequiredCredentialEntity",
|
||||||
@@ -48,8 +51,8 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
|
|||||||
"org.keycloak.models.entities.AuthenticationFlowEntity",
|
"org.keycloak.models.entities.AuthenticationFlowEntity",
|
||||||
"org.keycloak.models.entities.AuthenticatorConfigEntity",
|
"org.keycloak.models.entities.AuthenticatorConfigEntity",
|
||||||
"org.keycloak.models.entities.RequiredActionProviderEntity",
|
"org.keycloak.models.entities.RequiredActionProviderEntity",
|
||||||
"org.keycloak.models.entities.OfflineUserSessionEntity",
|
"org.keycloak.models.entities.PersistentUserSessionEntity",
|
||||||
"org.keycloak.models.entities.OfflineClientSessionEntity",
|
"org.keycloak.models.entities.PersistentClientSessionEntity",
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(DefaultMongoConnectionFactoryProvider.class);
|
private static final Logger logger = Logger.getLogger(DefaultMongoConnectionFactoryProvider.class);
|
||||||
@@ -147,31 +150,55 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
|
|||||||
* @throws UnknownHostException
|
* @throws UnknownHostException
|
||||||
*/
|
*/
|
||||||
protected MongoClient createMongoClient() throws UnknownHostException {
|
protected MongoClient createMongoClient() throws UnknownHostException {
|
||||||
String host = config.get("host", ServerAddress.defaultHost());
|
operationalInfo = new LinkedHashMap<>();
|
||||||
int port = config.getInt("port", ServerAddress.defaultPort());
|
|
||||||
String dbName = config.get("db", "keycloak");
|
String dbName = config.get("db", "keycloak");
|
||||||
|
|
||||||
String user = config.get("user");
|
String uriString = config.get("uri");
|
||||||
String password = config.get("password");
|
if (uriString != null) {
|
||||||
|
MongoClientURI uri = new MongoClientURI(uriString);
|
||||||
|
MongoClient client = new MongoClient(uri);
|
||||||
|
|
||||||
MongoClientOptions clientOptions = getClientOptions();
|
StringBuilder hostsBuilder = new StringBuilder();
|
||||||
|
for (int i=0 ; i<uri.getHosts().size() ; i++) {
|
||||||
|
if (i!=0) {
|
||||||
|
hostsBuilder.append(", ");
|
||||||
|
}
|
||||||
|
hostsBuilder.append(uri.getHosts().get(i));
|
||||||
|
}
|
||||||
|
String hosts = hostsBuilder.toString();
|
||||||
|
|
||||||
MongoClient client;
|
operationalInfo.put("mongoHosts", hosts);
|
||||||
if (user != null && password != null) {
|
operationalInfo.put("mongoDatabaseName", dbName);
|
||||||
MongoCredential credential = MongoCredential.createMongoCRCredential(user, dbName, password.toCharArray());
|
operationalInfo.put("mongoUser", uri.getUsername());
|
||||||
client = new MongoClient(new ServerAddress(host, port), Collections.singletonList(credential), clientOptions);
|
operationalInfo.put("mongoDriverVersion", client.getVersion());
|
||||||
|
|
||||||
|
logger.debugv("Initialized mongo model. host(s): %s, db: %s", uri.getHosts(), dbName);
|
||||||
|
return client;
|
||||||
} else {
|
} else {
|
||||||
client = new MongoClient(new ServerAddress(host, port), clientOptions);
|
String host = config.get("host", ServerAddress.defaultHost());
|
||||||
|
int port = config.getInt("port", ServerAddress.defaultPort());
|
||||||
|
|
||||||
|
String user = config.get("user");
|
||||||
|
String password = config.get("password");
|
||||||
|
|
||||||
|
MongoClientOptions clientOptions = getClientOptions();
|
||||||
|
|
||||||
|
MongoClient client;
|
||||||
|
if (user != null && password != null) {
|
||||||
|
MongoCredential credential = MongoCredential.createMongoCRCredential(user, dbName, password.toCharArray());
|
||||||
|
client = new MongoClient(new ServerAddress(host, port), Collections.singletonList(credential), clientOptions);
|
||||||
|
} else {
|
||||||
|
client = new MongoClient(new ServerAddress(host, port), clientOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
operationalInfo.put("mongoServerAddress", client.getAddress().toString());
|
||||||
|
operationalInfo.put("mongoDatabaseName", dbName);
|
||||||
|
operationalInfo.put("mongoUser", user);
|
||||||
|
operationalInfo.put("mongoDriverVersion", client.getVersion());
|
||||||
|
|
||||||
|
logger.debugv("Initialized mongo model. host: %s, port: %d, db: %s", host, port, dbName);
|
||||||
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
operationalInfo = new LinkedHashMap<>();
|
|
||||||
operationalInfo.put("mongoServerAddress", client.getAddress().toString());
|
|
||||||
operationalInfo.put("mongoDatabaseName", dbName);
|
|
||||||
operationalInfo.put("mongoUser", user);
|
|
||||||
operationalInfo.put("mongoDriverVersion", client.getVersion());
|
|
||||||
|
|
||||||
logger.debugv("Initialized mongo model. host: %s, port: %d, db: %s", host, port, dbName);
|
|
||||||
return client;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected MongoClientOptions getClientOptions() {
|
protected MongoClientOptions getClientOptions() {
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ public interface OAuth2Constants {
|
|||||||
// http://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
|
// http://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
|
||||||
String OFFLINE_ACCESS = "offline_access";
|
String OFFLINE_ACCESS = "offline_access";
|
||||||
|
|
||||||
|
String UI_LOCALES_PARAM = "ui_locales";
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
+4
@@ -22,6 +22,10 @@
|
|||||||
"provider": "jpa"
|
"provider": "jpa"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"userSessionPersister": {
|
||||||
|
"provider": "jpa"
|
||||||
|
},
|
||||||
|
|
||||||
"timer": {
|
"timer": {
|
||||||
"provider": "basic"
|
"provider": "basic"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -20,6 +20,13 @@
|
|||||||
<include>**/**</include>
|
<include>**/**</include>
|
||||||
</includes>
|
</includes>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
|
<!--<fileSet>
|
||||||
|
<directory>cli</directory>
|
||||||
|
<includes>
|
||||||
|
<include>*.cli</include>
|
||||||
|
</includes>
|
||||||
|
<outputDirectory>bin</outputDirectory>
|
||||||
|
</fileSet>-->
|
||||||
</fileSets>
|
</fileSets>
|
||||||
|
|
||||||
<files>
|
<files>
|
||||||
@@ -45,14 +52,6 @@
|
|||||||
<source>src/main/providers/README.txt</source>
|
<source>src/main/providers/README.txt</source>
|
||||||
<outputDirectory>standalone/configuration/providers</outputDirectory>
|
<outputDirectory>standalone/configuration/providers</outputDirectory>
|
||||||
</file>
|
</file>
|
||||||
<file>
|
|
||||||
<source>cli/keycloak-prepare.cli</source>
|
|
||||||
<outputDirectory>bin</outputDirectory>
|
|
||||||
</file>
|
|
||||||
<file>
|
|
||||||
<source>cli/keycloak-install.cli</source>
|
|
||||||
<outputDirectory>bin</outputDirectory>
|
|
||||||
</file>
|
|
||||||
</files>
|
</files>
|
||||||
|
|
||||||
</assembly>
|
</assembly>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/subsystem=datasources/data-source=KeycloakDS/:add(connection-url="jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE",driver-name=h2,jndi-name=java:jboss/datasources/KeycloakDS,password=sa,user-name=sa,use-java-context=true,enabled=true)
|
||||||
|
/subsystem=logging/logger=org.jboss.resteasy.resteasy_jaxrs.i18n/:add(level=ERROR)
|
||||||
|
/subsystem=infinispan/cache-container=keycloak:add(jndi-name="infinispan/Keycloak")
|
||||||
|
/subsystem=infinispan/cache-container=keycloak/transport=TRANSPORT:add(lock-timeout=60000)
|
||||||
|
/subsystem=infinispan/cache-container=keycloak/invalidation-cache=realms:add(mode="SYNC")
|
||||||
|
/subsystem=infinispan/cache-container=keycloak/invalidation-cache=users:add(mode="SYNC")
|
||||||
|
/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions:add(mode="SYNC",owners="1")
|
||||||
|
/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures:add(mode="SYNC",owners="1")
|
||||||
@@ -1,2 +1,7 @@
|
|||||||
/subsystem=datasources/data-source=KeycloakDS/:add(connection-url="jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE",driver-name=h2,jndi-name=java:jboss/datasources/KeycloakDS,password=sa,user-name=sa,use-java-context=true,enabled=true)
|
/subsystem=datasources/data-source=KeycloakDS/:add(connection-url="jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE",driver-name=h2,jndi-name=java:jboss/datasources/KeycloakDS,password=sa,user-name=sa,use-java-context=true,enabled=true)
|
||||||
/subsystem=logging/logger=org.jboss.resteasy.resteasy_jaxrs.i18n/:add(level=ERROR)
|
/subsystem=logging/logger=org.jboss.resteasy.resteasy_jaxrs.i18n/:add(level=ERROR)
|
||||||
|
/subsystem=infinispan/cache-container=keycloak:add(jndi-name="infinispan/Keycloak",start="EAGER")
|
||||||
|
/subsystem=infinispan/cache-container=keycloak/local-cache=realms:add()
|
||||||
|
/subsystem=infinispan/cache-container=keycloak/local-cache=users:add()
|
||||||
|
/subsystem=infinispan/cache-container=keycloak/local-cache=sessions:add()
|
||||||
|
/subsystem=infinispan/cache-container=keycloak/local-cache=loginFailures:add()
|
||||||
@@ -47,7 +47,13 @@
|
|||||||
</includes>
|
</includes>
|
||||||
<outputDirectory></outputDirectory>
|
<outputDirectory></outputDirectory>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
|
<!--<fileSet>
|
||||||
|
<directory>cli</directory>
|
||||||
|
<includes>
|
||||||
|
<include>*.cli</include>
|
||||||
|
</includes>
|
||||||
|
<outputDirectory>bin</outputDirectory>
|
||||||
|
</fileSet>-->
|
||||||
</fileSets>
|
</fileSets>
|
||||||
|
|
||||||
<files>
|
<files>
|
||||||
@@ -65,10 +71,6 @@
|
|||||||
<source>${project.build.directory}/unpacked/keycloak-${project.version}/standalone/configuration/keycloak-server.json</source>
|
<source>${project.build.directory}/unpacked/keycloak-${project.version}/standalone/configuration/keycloak-server.json</source>
|
||||||
<outputDirectory>standalone/configuration</outputDirectory>
|
<outputDirectory>standalone/configuration</outputDirectory>
|
||||||
</file>
|
</file>
|
||||||
<file>
|
|
||||||
<source>cli/keycloak-install.cli</source>
|
|
||||||
<outputDirectory>bin</outputDirectory>
|
|
||||||
</file>
|
|
||||||
</files>
|
</files>
|
||||||
|
|
||||||
</assembly>
|
</assembly>
|
||||||
@@ -1,3 +1,2 @@
|
|||||||
/subsystem=datasources/data-source=KeycloakDS/:add(connection-url="jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE",driver-name=h2,jndi-name=java:jboss/datasources/KeycloakDS,password=sa,user-name=sa,use-java-context=true)
|
|
||||||
/extension=org.keycloak.keycloak-server-subsystem/:add(module=org.keycloak.keycloak-server-subsystem)
|
/extension=org.keycloak.keycloak-server-subsystem/:add(module=org.keycloak.keycloak-server-subsystem)
|
||||||
/subsystem=keycloak-server:add(web-context=auth)
|
/subsystem=keycloak-server:add(web-context=auth)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
/subsystem=datasources/data-source=KeycloakDS/:add(connection-url="jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE",driver-name=h2,jndi-name=java:jboss/datasources/KeycloakDS,password=sa,user-name=sa,use-java-context=true)
|
||||||
|
/subsystem=infinispan/cache-container=keycloak:add(jndi-name="infinispan/Keycloak")
|
||||||
|
/subsystem=infinispan/cache-container=keycloak/transport=TRANSPORT:add(lock-timeout=60000)
|
||||||
|
/subsystem=infinispan/cache-container=keycloak/invalidation-cache=realms:add(mode="SYNC")
|
||||||
|
/subsystem=infinispan/cache-container=keycloak/invalidation-cache=users:add(mode="SYNC")
|
||||||
|
/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions:add(mode="SYNC",owners="1")
|
||||||
|
/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures:add(mode="SYNC",owners="1")
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/subsystem=datasources/data-source=KeycloakDS/:add(connection-url="jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE",driver-name=h2,jndi-name=java:jboss/datasources/KeycloakDS,password=sa,user-name=sa,use-java-context=true)
|
||||||
|
/subsystem=infinispan/cache-container=keycloak:add(jndi-name="infinispan/Keycloak")
|
||||||
|
/subsystem=infinispan/cache-container=keycloak/local-cache=realms:add()
|
||||||
|
/subsystem=infinispan/cache-container=keycloak/local-cache=users:add()
|
||||||
|
/subsystem=infinispan/cache-container=keycloak/local-cache=sessions:add()
|
||||||
|
/subsystem=infinispan/cache-container=keycloak/local-cache=loginFailures:add()
|
||||||
@@ -59,30 +59,23 @@
|
|||||||
(username: <emphasis>admin</emphasis> and password: <emphasis>admin</emphasis>). Keycloak will then prompt you to
|
(username: <emphasis>admin</emphasis> and password: <emphasis>admin</emphasis>). Keycloak will then prompt you to
|
||||||
enter in a new password.
|
enter in a new password.
|
||||||
</para>
|
</para>
|
||||||
<para>
|
<!--<para>
|
||||||
To add Keycloak to other sever configurations (standalone.xml, standalone-ha.xml, etc.) start the server with
|
To add Keycloak to other sever configurations (standalone.xml, standalone-ha.xml, etc.) start the server with
|
||||||
the desired server-config. Then execute the following CLI script:
|
the desired server-config. If you are running the server in standalone mode run:
|
||||||
|
<programlisting><WILDFLY_HOME>/bin/jboss-cli.sh -c --file=keycloak-prepare.cli</programlisting>
|
||||||
|
Or if you are running in clustering (HA) mode run:
|
||||||
|
<programlisting><WILDFLY_HOME>/bin/jboss-cli.sh -c --file=keycloak-prepare-ha.cli</programlisting>
|
||||||
|
After that you need to restart the server, you can do this with:
|
||||||
|
<programlisting><WILDFLY_HOME>/bin/jboss-cli.sh -c :reload</programlisting>
|
||||||
|
Finally you need to run:
|
||||||
<programlisting><WILDFLY_HOME>/bin/jboss-cli.sh -c --file=keycloak-install.cli</programlisting>
|
<programlisting><WILDFLY_HOME>/bin/jboss-cli.sh -c --file=keycloak-install.cli</programlisting>
|
||||||
</para>
|
</para>-->
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<title>Install on existing JBoss EAP 6.4.0.GA</title>
|
<title>Install on existing JBoss EAP 6.4.0.GA</title>
|
||||||
<para>
|
<para>
|
||||||
Same procedure as WildFly 9.0.0.Final, but download <literal>keycloak-overlay-eap6-&project.version;.zip</literal> or <literal>keycloak-overlay-eap6-&project.version;.tar.gz</literal>.
|
Same procedure as WildFly 9.0.0.Final, but download <literal>keycloak-overlay-eap6-&project.version;.zip</literal> or <literal>keycloak-overlay-eap6-&project.version;.tar.gz</literal>.
|
||||||
</para>
|
</para>
|
||||||
<para>
|
|
||||||
However, for EAP, adding Keycloak to other sever configurations (standalone.xml, standalone-ha.xml, etc.) requires two CLI scripts. Start the server with
|
|
||||||
the desired server-config. Then execute the following CLI scripts with a restart in between:
|
|
||||||
<orderedlist>
|
|
||||||
<listitem>
|
|
||||||
<programlisting><EAP_HOME>/bin/jboss-cli.sh -c --file=keycloak-prepare.cli</programlisting>
|
|
||||||
</listitem>
|
|
||||||
<listitem>Restart the server with the same server-config.</listitem>
|
|
||||||
<listitem>
|
|
||||||
<programlisting><EAP_HOME>/bin/jboss-cli.sh -c --file=keycloak-install.cli</programlisting>
|
|
||||||
</listitem>
|
|
||||||
</orderedlist>
|
|
||||||
</para>
|
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<title id="demo_install">Install Development Bundle</title>
|
<title id="demo_install">Install Development Bundle</title>
|
||||||
@@ -329,6 +322,30 @@
|
|||||||
Supported long option is <literal>maxAutoConnectRetryTime</literal>. See <ulink url="http://api.mongodb.org/java/2.11.4/com/mongodb/MongoClientOptions.html">Mongo documentation</ulink>
|
Supported long option is <literal>maxAutoConnectRetryTime</literal>. See <ulink url="http://api.mongodb.org/java/2.11.4/com/mongodb/MongoClientOptions.html">Mongo documentation</ulink>
|
||||||
for details about those options and their default values.
|
for details about those options and their default values.
|
||||||
</para>
|
</para>
|
||||||
|
<para>
|
||||||
|
Alternatively, you can configure MongoDB using a MongoDB <ulink url="http://docs.mongodb.org/manual/reference/connection-string/">connection URI</ulink>.
|
||||||
|
In this case, you define all information concerning the connection and authentication within the URI, as described in the MongoDB documentation.
|
||||||
|
Please note that the database specified within the URI is only used for authentication. To change the database used by keycloak you have to set
|
||||||
|
<literal>db</literal> property as before. Therefore, a configuration like the
|
||||||
|
following
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
"connectionsMongo": {
|
||||||
|
"default": {
|
||||||
|
"uri": "mongodb://user:password@127.0.0.1/authentication",
|
||||||
|
"db": "keycloak"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]]></programlisting>
|
||||||
|
will authenticate the user against the authentication database, but store all keycloak related data in the keycloak database.
|
||||||
|
|
||||||
|
</para>
|
||||||
|
<section>
|
||||||
|
<title>MongoDB Replica Sets</title>
|
||||||
|
<para>
|
||||||
|
In order to use a mongo replica set for Keycloak, one has to use URI based configuration, which supports the
|
||||||
|
definition of replica sets out of the box: <literal>mongodb://host1:27017,host2:27017,host3:27017/</literal>.
|
||||||
|
</para>
|
||||||
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
|
|||||||
+23
-24
@@ -1,7 +1,7 @@
|
|||||||
package org.keycloak.exportimport.util;
|
package org.keycloak.exportimport.util;
|
||||||
|
|
||||||
import org.keycloak.models.OfflineClientSessionModel;
|
import org.keycloak.models.session.PersistentClientSessionModel;
|
||||||
import org.keycloak.models.OfflineUserSessionModel;
|
import org.keycloak.models.session.PersistentUserSessionModel;
|
||||||
import org.keycloak.representations.idm.OfflineUserSessionRepresentation;
|
import org.keycloak.representations.idm.OfflineUserSessionRepresentation;
|
||||||
import org.keycloak.util.Base64;
|
import org.keycloak.util.Base64;
|
||||||
import org.codehaus.jackson.JsonEncoding;
|
import org.codehaus.jackson.JsonEncoding;
|
||||||
@@ -11,7 +11,6 @@ import org.codehaus.jackson.map.ObjectMapper;
|
|||||||
import org.codehaus.jackson.map.SerializationConfig;
|
import org.codehaus.jackson.map.SerializationConfig;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.ProtocolMapperModel;
|
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleContainerModel;
|
import org.keycloak.models.RoleContainerModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
@@ -298,27 +297,27 @@ public class ExportUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Offline sessions
|
// // Offline sessions
|
||||||
List<OfflineUserSessionRepresentation> offlineSessionReps = new LinkedList<>();
|
// List<OfflineUserSessionRepresentation> offlineSessionReps = new LinkedList<>();
|
||||||
Collection<OfflineUserSessionModel> offlineSessions = session.users().getOfflineUserSessions(realm, user);
|
// Collection<PersistentUserSessionModel> offlineSessions = session.users().getOfflineUserSessions(realm, user);
|
||||||
Collection<OfflineClientSessionModel> offlineClientSessions = session.users().getOfflineClientSessions(realm, user);
|
// Collection<PersistentClientSessionModel> offlineClientSessions = session.users().getOfflineClientSessions(realm, user);
|
||||||
|
//
|
||||||
Map<String, List<OfflineClientSessionModel>> processed = new HashMap<>();
|
// Map<String, List<PersistentClientSessionModel>> processed = new HashMap<>();
|
||||||
for (OfflineClientSessionModel clsm : offlineClientSessions) {
|
// for (PersistentClientSessionModel clsm : offlineClientSessions) {
|
||||||
String userSessionId = clsm.getUserSessionId();
|
// String userSessionId = clsm.getUserSessionId();
|
||||||
List<OfflineClientSessionModel> current = processed.get(userSessionId);
|
// List<PersistentClientSessionModel> current = processed.get(userSessionId);
|
||||||
if (current == null) {
|
// if (current == null) {
|
||||||
current = new LinkedList<>();
|
// current = new LinkedList<>();
|
||||||
processed.put(userSessionId, current);
|
// processed.put(userSessionId, current);
|
||||||
}
|
// }
|
||||||
current.add(clsm);
|
// current.add(clsm);
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
for (OfflineUserSessionModel userSession : offlineSessions) {
|
// for (PersistentUserSessionModel userSession : offlineSessions) {
|
||||||
OfflineUserSessionRepresentation sessionRep = ModelToRepresentation.toRepresentation(realm, userSession, processed.get(userSession.getUserSessionId()));
|
// OfflineUserSessionRepresentation sessionRep = ModelToRepresentation.toRepresentation(realm, userSession, processed.get(userSession.getUserSessionId()));
|
||||||
offlineSessionReps.add(sessionRep);
|
// offlineSessionReps.add(sessionRep);
|
||||||
}
|
// }
|
||||||
userRep.setOfflineUserSessions(offlineSessionReps);
|
// userRep.setOfflineUserSessions(offlineSessionReps);
|
||||||
|
|
||||||
return userRep;
|
return userRep;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-7
@@ -51,7 +51,6 @@ import org.keycloak.events.Event;
|
|||||||
import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
|
import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
|
||||||
import org.keycloak.freemarker.FreeMarkerException;
|
import org.keycloak.freemarker.FreeMarkerException;
|
||||||
import org.keycloak.freemarker.FreeMarkerUtil;
|
import org.keycloak.freemarker.FreeMarkerUtil;
|
||||||
import org.keycloak.freemarker.LocaleHelper;
|
|
||||||
import org.keycloak.freemarker.Theme;
|
import org.keycloak.freemarker.Theme;
|
||||||
import org.keycloak.freemarker.ThemeProvider;
|
import org.keycloak.freemarker.ThemeProvider;
|
||||||
import org.keycloak.freemarker.beans.AdvancedMessageFormatterMethod;
|
import org.keycloak.freemarker.beans.AdvancedMessageFormatterMethod;
|
||||||
@@ -65,7 +64,6 @@ import org.keycloak.models.RealmModel;
|
|||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.utils.FormMessage;
|
import org.keycloak.models.utils.FormMessage;
|
||||||
import org.keycloak.services.Urls;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
@@ -130,7 +128,7 @@ public class FreeMarkerAccountProvider implements AccountProvider {
|
|||||||
logger.warn("Failed to load properties", e);
|
logger.warn("Failed to load properties", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
Locale locale = LocaleHelper.getLocale(realm, user, uriInfo, headers);
|
Locale locale = session.getContext().resolveLocale(user);
|
||||||
Properties messagesBundle;
|
Properties messagesBundle;
|
||||||
try {
|
try {
|
||||||
messagesBundle = theme.getMessages(locale);
|
messagesBundle = theme.getMessages(locale);
|
||||||
@@ -213,10 +211,6 @@ public class FreeMarkerAccountProvider implements AccountProvider {
|
|||||||
String result = freeMarker.processTemplate(attributes, Templates.getTemplate(page), theme);
|
String result = freeMarker.processTemplate(attributes, Templates.getTemplate(page), theme);
|
||||||
Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML).entity(result);
|
Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML).entity(result);
|
||||||
BrowserSecurityHeaderSetup.headers(builder, realm);
|
BrowserSecurityHeaderSetup.headers(builder, realm);
|
||||||
|
|
||||||
String keycloakLocaleCookiePath = Urls.localeCookiePath(baseUri, realm.getName());
|
|
||||||
|
|
||||||
LocaleHelper.updateLocaleCookie(builder, locale, realm, uriInfo, keycloakLocaleCookiePath);
|
|
||||||
return builder.build();
|
return builder.build();
|
||||||
} catch (FreeMarkerException e) {
|
} catch (FreeMarkerException e) {
|
||||||
logger.error("Failed to process template", e);
|
logger.error("Failed to process template", e);
|
||||||
|
|||||||
+2
-2
@@ -13,7 +13,7 @@ import org.keycloak.models.RealmModel;
|
|||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.protocol.oidc.TokenManager;
|
import org.keycloak.protocol.oidc.TokenManager;
|
||||||
import org.keycloak.services.offline.OfflineTokenUtils;
|
import org.keycloak.services.managers.UserSessionManager;
|
||||||
import org.keycloak.util.MultivaluedHashMap;
|
import org.keycloak.util.MultivaluedHashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -25,7 +25,7 @@ public class ApplicationsBean {
|
|||||||
|
|
||||||
public ApplicationsBean(KeycloakSession session, RealmModel realm, UserModel user) {
|
public ApplicationsBean(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||||
|
|
||||||
Set<ClientModel> offlineClients = OfflineTokenUtils.findClientsWithOfflineToken(session, realm, user);
|
Set<ClientModel> offlineClients = new UserSessionManager(session).findClientsWithOfflineToken(realm, user);
|
||||||
|
|
||||||
List<ClientModel> realmClients = realm.getClients();
|
List<ClientModel> realmClients = realm.getClients();
|
||||||
for (ClientModel client : realmClients) {
|
for (ClientModel client : realmClients) {
|
||||||
|
|||||||
@@ -1,140 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
|
|
||||||
* as indicated by the @author tags. All rights reserved.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
package org.keycloak.freemarker;
|
|
||||||
|
|
||||||
import org.keycloak.models.RealmModel;
|
|
||||||
import org.keycloak.models.UserModel;
|
|
||||||
|
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
|
||||||
import javax.ws.rs.core.NewCookie;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import javax.ws.rs.core.UriInfo;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:gerbermichi@me.com">Michael Gerber</a>
|
|
||||||
*/
|
|
||||||
public class LocaleHelper {
|
|
||||||
public final static String LOCALE_COOKIE = "KEYCLOAK_LOCALE";
|
|
||||||
public static final String UI_LOCALES_PARAM = "ui_locales";
|
|
||||||
public static final String KC_LOCALE_PARAM = "kc_locale";
|
|
||||||
|
|
||||||
public static Locale getLocale(RealmModel realm, UserModel user) {
|
|
||||||
return getLocale(realm, user, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Locale getLocale(RealmModel realm, UserModel user, UriInfo uriInfo, HttpHeaders httpHeaders) {
|
|
||||||
if(!realm.isInternationalizationEnabled()){
|
|
||||||
return Locale.ENGLISH;
|
|
||||||
}
|
|
||||||
|
|
||||||
//0. kc_locale query parameter
|
|
||||||
if(uriInfo != null && uriInfo.getQueryParameters().containsKey(KC_LOCALE_PARAM)){
|
|
||||||
String localeString = uriInfo.getQueryParameters().getFirst(KC_LOCALE_PARAM);
|
|
||||||
Locale locale = findLocale(realm.getSupportedLocales(), localeString);
|
|
||||||
if(locale != null){
|
|
||||||
if(user != null){
|
|
||||||
user.setSingleAttribute(UserModel.LOCALE, locale.toLanguageTag());
|
|
||||||
}
|
|
||||||
return locale;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//1. Locale cookie
|
|
||||||
if(httpHeaders != null && httpHeaders.getCookies().containsKey(LOCALE_COOKIE)){
|
|
||||||
String localeString = httpHeaders.getCookies().get(LOCALE_COOKIE).getValue();
|
|
||||||
Locale locale = findLocale(realm.getSupportedLocales(), localeString);
|
|
||||||
if(locale != null){
|
|
||||||
if(user != null && user.getFirstAttribute(UserModel.LOCALE) == null){
|
|
||||||
user.setSingleAttribute(UserModel.LOCALE, locale.toLanguageTag());
|
|
||||||
}
|
|
||||||
return locale;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//2. User profile
|
|
||||||
if(user != null && user.getAttributes().containsKey(UserModel.LOCALE)){
|
|
||||||
String localeString = user.getFirstAttribute(UserModel.LOCALE);
|
|
||||||
Locale locale = findLocale(realm.getSupportedLocales(), localeString);
|
|
||||||
if(locale != null){
|
|
||||||
|
|
||||||
return locale;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//3. ui_locales query parameter
|
|
||||||
if(uriInfo != null && uriInfo.getQueryParameters().containsKey(UI_LOCALES_PARAM)){
|
|
||||||
String localeString = uriInfo.getQueryParameters().getFirst(UI_LOCALES_PARAM);
|
|
||||||
Locale locale = findLocale(realm.getSupportedLocales(), localeString.split(" "));
|
|
||||||
if(locale != null){
|
|
||||||
return locale;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//4. Accept-Language http header
|
|
||||||
if(httpHeaders !=null && httpHeaders.getAcceptableLanguages() != null && !httpHeaders.getAcceptableLanguages().isEmpty()){
|
|
||||||
for(Locale l : httpHeaders.getAcceptableLanguages()){
|
|
||||||
String localeString = l.toLanguageTag();
|
|
||||||
Locale locale = findLocale(realm.getSupportedLocales(), localeString);
|
|
||||||
if(locale != null){
|
|
||||||
return locale;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//5. Default realm locale
|
|
||||||
if(realm.getDefaultLocale() != null){
|
|
||||||
return Locale.forLanguageTag(realm.getDefaultLocale());
|
|
||||||
}
|
|
||||||
|
|
||||||
return Locale.ENGLISH;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void updateLocaleCookie(Response.ResponseBuilder builder,
|
|
||||||
Locale locale,
|
|
||||||
RealmModel realm,
|
|
||||||
UriInfo uriInfo,
|
|
||||||
String path) {
|
|
||||||
if (locale == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
boolean secure = realm.getSslRequired().isRequired(uriInfo.getRequestUri().getHost());
|
|
||||||
builder.cookie(new NewCookie(LocaleHelper.LOCALE_COOKIE, locale.toLanguageTag(), path, null, null, 31536000, secure));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Locale findLocale(Set<String> supportedLocales, String ... localeStrings) {
|
|
||||||
for(String localeString : localeStrings){
|
|
||||||
Locale result = null;
|
|
||||||
Locale search = Locale.forLanguageTag(localeString);
|
|
||||||
for(String languageTag : supportedLocales) {
|
|
||||||
Locale locale = Locale.forLanguageTag(languageTag);
|
|
||||||
if(locale.getLanguage().equals(search.getLanguage())){
|
|
||||||
if(locale.getCountry().equals("") && result == null){
|
|
||||||
result = locale;
|
|
||||||
}
|
|
||||||
if(locale.getCountry().equals(search.getCountry())){
|
|
||||||
return locale;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(result != null){
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.keycloak.freemarker.beans;
|
package org.keycloak.freemarker.beans;
|
||||||
|
|
||||||
import org.keycloak.freemarker.LocaleHelper;
|
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
@@ -22,7 +21,7 @@ public class LocaleBean {
|
|||||||
supported = new LinkedList<>();
|
supported = new LinkedList<>();
|
||||||
for (String l : realm.getSupportedLocales()) {
|
for (String l : realm.getSupportedLocales()) {
|
||||||
String label = messages.getProperty("locale_" + l, l);
|
String label = messages.getProperty("locale_" + l, l);
|
||||||
String url = uriBuilder.replaceQueryParam(LocaleHelper.KC_LOCALE_PARAM, l).build().toString();
|
String url = uriBuilder.replaceQueryParam("kc_locale", l).build().toString();
|
||||||
supported.add(new Locale(label, url));
|
supported.add(new Locale(label, url));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
package org.keycloak.freemarke;
|
|
||||||
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.keycloak.freemarker.LocaleHelper;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashSet;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:gerbermichi@me.com">Michael Gerber</a>
|
|
||||||
*/
|
|
||||||
public class LocaleHelperTest {
|
|
||||||
@Test
|
|
||||||
public void findLocaleTest(){
|
|
||||||
Assert.assertEquals("de", LocaleHelper.findLocale(new HashSet<>(Arrays.asList("de","en")), "de").toLanguageTag());
|
|
||||||
Assert.assertEquals("en", LocaleHelper.findLocale(new HashSet<>(Arrays.asList("de","en")), "en").toLanguageTag());
|
|
||||||
Assert.assertEquals("de", LocaleHelper.findLocale(new HashSet<>(Arrays.asList("de","en")), "de-CH").toLanguageTag());
|
|
||||||
Assert.assertEquals("de-CH", LocaleHelper.findLocale(new HashSet<>(Arrays.asList("de","de-CH","de-DE")), "de-CH").toLanguageTag());
|
|
||||||
Assert.assertEquals("de-DE", LocaleHelper.findLocale(new HashSet<>(Arrays.asList("de","de-CH","de-DE")), "de-DE").toLanguageTag());
|
|
||||||
Assert.assertEquals("de", LocaleHelper.findLocale(new HashSet<>(Arrays.asList("de","de-CH","de-DE")), "de").toLanguageTag());
|
|
||||||
Assert.assertNull(LocaleHelper.findLocale(new HashSet<>(Arrays.asList("de-CH","de-DE")), "de"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void findLocalesTest(){
|
|
||||||
Assert.assertEquals("de", LocaleHelper.findLocale(new HashSet<>(Arrays.asList("de","en")), "de en".split(" ")).toLanguageTag());
|
|
||||||
Assert.assertEquals("en", LocaleHelper.findLocale(new HashSet<>(Arrays.asList("de","en")), "en de".split(" ")).toLanguageTag());
|
|
||||||
Assert.assertEquals("de", LocaleHelper.findLocale(new HashSet<>(Arrays.asList("de","en")), "de-CH en".split(" ")).toLanguageTag());
|
|
||||||
Assert.assertEquals("de-CH", LocaleHelper.findLocale(new HashSet<>(Arrays.asList("de","de-CH","de-DE")), "de-CH de".split(" ")).toLanguageTag());
|
|
||||||
Assert.assertEquals("de-DE", LocaleHelper.findLocale(new HashSet<>(Arrays.asList("de","de-CH","de-DE")), "de-DE de-CH de".split(" ")).toLanguageTag());
|
|
||||||
Assert.assertEquals("de", LocaleHelper.findLocale(new HashSet<>(Arrays.asList("de","de-CH","de-DE")), "en fr de".split(" ")).toLanguageTag());
|
|
||||||
Assert.assertNull(LocaleHelper.findLocale(new HashSet<>(Arrays.asList("de-CH","de-DE")), "de en fr".split(" ")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2030,16 +2030,11 @@ module.filter('capitalize', function() {
|
|||||||
if (!input) {
|
if (!input) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var result = input.substring(0, 1).toUpperCase();
|
var splittedWords = input.split(/\s+/);
|
||||||
var s = input.substring(1);
|
for (var i=0; i<splittedWords.length ; i++) {
|
||||||
for (var i=0; i<s.length ; i++) {
|
splittedWords[i] = splittedWords[i].charAt(0).toUpperCase() + splittedWords[i].slice(1);
|
||||||
var c = s[i];
|
|
||||||
if (c.match(/[A-Z]/)) {
|
|
||||||
result = result.concat(" ")
|
|
||||||
};
|
|
||||||
result = result.concat(c);
|
|
||||||
};
|
};
|
||||||
return result;
|
return splittedWords.join(" ");
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -46,7 +46,7 @@
|
|||||||
<input ui-select2="eventSelectOptions" id="enabledEventTypes" ng-model="eventsConfig.enabledEventTypes" data-placeholder="Select event types..."/>
|
<input ui-select2="eventSelectOptions" id="enabledEventTypes" ng-model="eventsConfig.enabledEventTypes" data-placeholder="Select event types..."/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span tooltip-trigger="mouseover mouseout" tooltip-placement="right" tooltip="Configure what event types are saved. By default events related to login and users modifying their accounts are persisted." class="fa fa-info-circle"></span>
|
<span tooltip-trigger="mouseover mouseout" tooltip-placement="right" tooltip="Configure what event types are saved." class="fa fa-info-circle"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group" data-ng-show="access.manageEvents && eventsConfig.eventsEnabled">
|
<div class="form-group" data-ng-show="access.manageEvents && eventsConfig.eventsEnabled">
|
||||||
|
|||||||
@@ -29,6 +29,11 @@
|
|||||||
<artifactId>keycloak-model-api</artifactId>
|
<artifactId>keycloak-model-api</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-services</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-events-api</artifactId>
|
<artifactId>keycloak-events-api</artifactId>
|
||||||
|
|||||||
+1
-2
@@ -24,7 +24,6 @@ import org.keycloak.events.Event;
|
|||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.freemarker.FreeMarkerException;
|
import org.keycloak.freemarker.FreeMarkerException;
|
||||||
import org.keycloak.freemarker.FreeMarkerUtil;
|
import org.keycloak.freemarker.FreeMarkerUtil;
|
||||||
import org.keycloak.freemarker.LocaleHelper;
|
|
||||||
import org.keycloak.freemarker.Theme;
|
import org.keycloak.freemarker.Theme;
|
||||||
import org.keycloak.freemarker.ThemeProvider;
|
import org.keycloak.freemarker.ThemeProvider;
|
||||||
import org.keycloak.freemarker.beans.MessageFormatterMethod;
|
import org.keycloak.freemarker.beans.MessageFormatterMethod;
|
||||||
@@ -110,7 +109,7 @@ public class FreeMarkerEmailProvider implements EmailProvider {
|
|||||||
try {
|
try {
|
||||||
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
|
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
|
||||||
Theme theme = themeProvider.getTheme(realm.getEmailTheme(), Theme.Type.EMAIL);
|
Theme theme = themeProvider.getTheme(realm.getEmailTheme(), Theme.Type.EMAIL);
|
||||||
Locale locale = LocaleHelper.getLocale(realm, user);
|
Locale locale = session.getContext().resolveLocale(user);
|
||||||
attributes.put("locale", locale);
|
attributes.put("locale", locale);
|
||||||
Properties rb = theme.getMessages(locale);
|
Properties rb = theme.getMessages(locale);
|
||||||
attributes.put("msg", new MessageFormatterMethod(locale, rb));
|
attributes.put("msg", new MessageFormatterMethod(locale, rb));
|
||||||
|
|||||||
+2
-10
@@ -24,7 +24,6 @@ import org.keycloak.email.EmailProvider;
|
|||||||
import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
|
import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
|
||||||
import org.keycloak.freemarker.FreeMarkerException;
|
import org.keycloak.freemarker.FreeMarkerException;
|
||||||
import org.keycloak.freemarker.FreeMarkerUtil;
|
import org.keycloak.freemarker.FreeMarkerUtil;
|
||||||
import org.keycloak.freemarker.LocaleHelper;
|
|
||||||
import org.keycloak.freemarker.Theme;
|
import org.keycloak.freemarker.Theme;
|
||||||
import org.keycloak.freemarker.ThemeProvider;
|
import org.keycloak.freemarker.ThemeProvider;
|
||||||
import org.keycloak.freemarker.beans.AdvancedMessageFormatterMethod;
|
import org.keycloak.freemarker.beans.AdvancedMessageFormatterMethod;
|
||||||
@@ -201,7 +200,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Properties messagesBundle;
|
Properties messagesBundle;
|
||||||
Locale locale = LocaleHelper.getLocale(realm, user, uriInfo, session.getContext().getRequestHeaders());
|
Locale locale = session.getContext().resolveLocale(user);
|
||||||
try {
|
try {
|
||||||
messagesBundle = theme.getMessages(locale);
|
messagesBundle = theme.getMessages(locale);
|
||||||
attributes.put("msg", new MessageFormatterMethod(locale, messagesBundle));
|
attributes.put("msg", new MessageFormatterMethod(locale, messagesBundle));
|
||||||
@@ -292,10 +291,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||||||
for (Map.Entry<String, String> entry : httpResponseHeaders.entrySet()) {
|
for (Map.Entry<String, String> entry : httpResponseHeaders.entrySet()) {
|
||||||
builder.header(entry.getKey(), entry.getValue());
|
builder.header(entry.getKey(), entry.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
String cookiePath = Urls.localeCookiePath(baseUri, realm.getName());
|
|
||||||
LocaleHelper.updateLocaleCookie(builder, locale, realm, uriInfo, cookiePath);
|
|
||||||
|
|
||||||
return builder.build();
|
return builder.build();
|
||||||
} catch (FreeMarkerException e) {
|
} catch (FreeMarkerException e) {
|
||||||
logger.error("Failed to process template", e);
|
logger.error("Failed to process template", e);
|
||||||
@@ -345,7 +340,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Properties messagesBundle;
|
Properties messagesBundle;
|
||||||
Locale locale = LocaleHelper.getLocale(realm, user, uriInfo, session.getContext().getRequestHeaders());
|
Locale locale = session.getContext().resolveLocale(user);
|
||||||
try {
|
try {
|
||||||
messagesBundle = theme.getMessages(locale);
|
messagesBundle = theme.getMessages(locale);
|
||||||
attributes.put("msg", new MessageFormatterMethod(locale, messagesBundle));
|
attributes.put("msg", new MessageFormatterMethod(locale, messagesBundle));
|
||||||
@@ -393,9 +388,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||||||
for (Map.Entry<String, String> entry : httpResponseHeaders.entrySet()) {
|
for (Map.Entry<String, String> entry : httpResponseHeaders.entrySet()) {
|
||||||
builder.header(entry.getKey(), entry.getValue());
|
builder.header(entry.getKey(), entry.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
String cookiePath = Urls.localeCookiePath(baseUri, realm.getName());
|
|
||||||
LocaleHelper.updateLocaleCookie(builder, locale, realm, uriInfo, cookiePath);
|
|
||||||
return builder.build();
|
return builder.build();
|
||||||
} catch (FreeMarkerException e) {
|
} catch (FreeMarkerException e) {
|
||||||
logger.error("Failed to process template", e);
|
logger.error("Failed to process template", e);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import org.keycloak.models.utils.RealmImporter;
|
|||||||
|
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
@@ -33,4 +34,6 @@ public interface KeycloakContext {
|
|||||||
|
|
||||||
RealmImporter getRealmManager();
|
RealmImporter getRealmManager();
|
||||||
|
|
||||||
|
Locale resolveLocale(UserModel user);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -97,6 +97,9 @@ public interface RealmModel extends RoleContainerModel {
|
|||||||
int getSsoSessionMaxLifespan();
|
int getSsoSessionMaxLifespan();
|
||||||
void setSsoSessionMaxLifespan(int seconds);
|
void setSsoSessionMaxLifespan(int seconds);
|
||||||
|
|
||||||
|
// int getOfflineSessionIdleTimeout();
|
||||||
|
// void setOfflineSessionIdleTimeout(int seconds);
|
||||||
|
|
||||||
int getAccessTokenLifespan();
|
int getAccessTokenLifespan();
|
||||||
|
|
||||||
void setAccessTokenLifespan(int seconds);
|
void setAccessTokenLifespan(int seconds);
|
||||||
@@ -286,6 +289,10 @@ public interface RealmModel extends RoleContainerModel {
|
|||||||
|
|
||||||
void setEventsEnabled(boolean enabled);
|
void setEventsEnabled(boolean enabled);
|
||||||
|
|
||||||
|
// boolean isPersistUserSessions();
|
||||||
|
//
|
||||||
|
// void setPersistUserSessions();
|
||||||
|
|
||||||
long getEventsExpiration();
|
long getEventsExpiration();
|
||||||
|
|
||||||
void setEventsExpiration(long expiration);
|
void setEventsExpiration(long expiration);
|
||||||
|
|||||||
@@ -475,70 +475,6 @@ public class UserFederationManager implements UserProvider {
|
|||||||
return (result != null) ? result : CredentialValidationOutput.failed();
|
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<OfflineUserSessionModel> 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<OfflineClientSessionModel> 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<OfflineClientSessionModel> getOfflineClientSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
|
|
||||||
return session.userStorage().getOfflineClientSessions(realm, client, firstResult, maxResults);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.keycloak.models;
|
package org.keycloak.models;
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -10,11 +9,11 @@ import java.util.Set;
|
|||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public interface UserModel {
|
public interface UserModel {
|
||||||
public static final String USERNAME = "username";
|
String USERNAME = "username";
|
||||||
public static final String LAST_NAME = "lastName";
|
String LAST_NAME = "lastName";
|
||||||
public static final String FIRST_NAME = "firstName";
|
String FIRST_NAME = "firstName";
|
||||||
public static final String EMAIL = "email";
|
String EMAIL = "email";
|
||||||
public static final String LOCALE = "locale";
|
String LOCALE = "locale";
|
||||||
|
|
||||||
String getId();
|
String getId();
|
||||||
|
|
||||||
|
|||||||
@@ -56,17 +56,5 @@ public interface UserProvider extends Provider {
|
|||||||
boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input);
|
boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input);
|
||||||
CredentialValidationOutput validCredentials(RealmModel realm, 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<OfflineUserSessionModel> 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<OfflineClientSessionModel> getOfflineClientSessions(RealmModel realm, UserModel user);
|
|
||||||
boolean removeOfflineClientSession(RealmModel realm, UserModel user, String clientSessionId);
|
|
||||||
|
|
||||||
int getOfflineClientSessionsCount(RealmModel realm, ClientModel client);
|
|
||||||
Collection<OfflineClientSessionModel> getOfflineClientSessions(RealmModel realm, ClientModel client, int first, int max);
|
|
||||||
|
|
||||||
void close();
|
void close();
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,7 @@ import java.util.Map;
|
|||||||
public interface UserSessionModel {
|
public interface UserSessionModel {
|
||||||
|
|
||||||
String getId();
|
String getId();
|
||||||
|
RealmModel getRealm();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If created via a broker external login, this is an identifier that can be
|
* If created via a broker external login, this is an identifier that can be
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.keycloak.models;
|
|||||||
|
|
||||||
import org.keycloak.provider.Provider;
|
import org.keycloak.provider.Provider;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -27,6 +28,8 @@ public interface UserSessionProvider extends Provider {
|
|||||||
int getActiveUserSessions(RealmModel realm, ClientModel client);
|
int getActiveUserSessions(RealmModel realm, ClientModel client);
|
||||||
void removeUserSession(RealmModel realm, UserSessionModel session);
|
void removeUserSession(RealmModel realm, UserSessionModel session);
|
||||||
void removeUserSessions(RealmModel realm, UserModel user);
|
void removeUserSessions(RealmModel realm, UserModel user);
|
||||||
|
|
||||||
|
// Implementation should propagate removal of expired userSessions to userSessionPersister too
|
||||||
void removeExpiredUserSessions(RealmModel realm);
|
void removeExpiredUserSessions(RealmModel realm);
|
||||||
void removeUserSessions(RealmModel realm);
|
void removeUserSessions(RealmModel realm);
|
||||||
void removeClientSession(RealmModel realm, ClientSessionModel clientSession);
|
void removeClientSession(RealmModel realm, ClientSessionModel clientSession);
|
||||||
@@ -40,6 +43,22 @@ public interface UserSessionProvider extends Provider {
|
|||||||
void onClientRemoved(RealmModel realm, ClientModel client);
|
void onClientRemoved(RealmModel realm, ClientModel client);
|
||||||
void onUserRemoved(RealmModel realm, UserModel user);
|
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<ClientSessionModel> 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<UserSessionModel> getOfflineUserSessions(RealmModel realm, ClientModel client, int first, int max);
|
||||||
|
|
||||||
void close();
|
void close();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -7,4 +7,8 @@ import org.keycloak.provider.ProviderFactory;
|
|||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public interface UserSessionProviderFactory extends ProviderFactory<UserSessionProvider> {
|
public interface UserSessionProviderFactory extends ProviderFactory<UserSessionProvider> {
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
package org.keycloak.models.entities;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class OfflineUserSessionEntity {
|
|
||||||
|
|
||||||
private String userSessionId;
|
|
||||||
private String data;
|
|
||||||
private List<OfflineClientSessionEntity> 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<OfflineClientSessionEntity> getOfflineClientSessions() {
|
|
||||||
return offlineClientSessions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOfflineClientSessions(List<OfflineClientSessionEntity> offlineClientSessions) {
|
|
||||||
this.offlineClientSessions = offlineClientSessions;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
-1
@@ -3,7 +3,7 @@ package org.keycloak.models.entities;
|
|||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
public class OfflineClientSessionEntity {
|
public class PersistentClientSessionEntity {
|
||||||
|
|
||||||
private String clientSessionId;
|
private String clientSessionId;
|
||||||
private String clientId;
|
private String clientId;
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package org.keycloak.models.entities;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class PersistentUserSessionEntity {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
private String realmId;
|
||||||
|
private String userId;
|
||||||
|
private int lastSessionRefresh;
|
||||||
|
private String data;
|
||||||
|
private List<PersistentClientSessionEntity> 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<PersistentClientSessionEntity> getClientSessions() {
|
||||||
|
return clientSessions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientSessions(List<PersistentClientSessionEntity> clientSessions) {
|
||||||
|
this.clientSessions = clientSessions;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,7 +28,6 @@ public class UserEntity extends AbstractIdentifiableEntity {
|
|||||||
private List<FederatedIdentityEntity> federatedIdentities;
|
private List<FederatedIdentityEntity> federatedIdentities;
|
||||||
private String federationLink;
|
private String federationLink;
|
||||||
private String serviceAccountClientLink;
|
private String serviceAccountClientLink;
|
||||||
private List<OfflineUserSessionEntity> offlineUserSessions;
|
|
||||||
|
|
||||||
public String getUsername() {
|
public String getUsername() {
|
||||||
return username;
|
return username;
|
||||||
@@ -158,13 +157,5 @@ public class UserEntity extends AbstractIdentifiableEntity {
|
|||||||
public void setServiceAccountClientLink(String serviceAccountClientLink) {
|
public void setServiceAccountClientLink(String serviceAccountClientLink) {
|
||||||
this.serviceAccountClientLink = serviceAccountClientLink;
|
this.serviceAccountClientLink = serviceAccountClientLink;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<OfflineUserSessionEntity> getOfflineUserSessions() {
|
|
||||||
return offlineUserSessions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOfflineUserSessions(List<OfflineUserSessionEntity> offlineUserSessions) {
|
|
||||||
this.offlineUserSessions = offlineUserSessions;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
+104
@@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
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<UserSessionModel> loadUserSessions(int firstResult, int maxResults, boolean offline) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getUserSessionsCount(boolean offline) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
+401
@@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
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<String> getRoles() {
|
||||||
|
return getData().getRoles();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRoles(Set<String> roles) {
|
||||||
|
getData().setRoles(roles);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getProtocolMappers() {
|
||||||
|
return getData().getProtocolMappers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setProtocolMappers(Set<String> protocolMappers) {
|
||||||
|
getData().setProtocolMappers(protocolMappers);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, ExecutionStatus> 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<String, String>());
|
||||||
|
}
|
||||||
|
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<String, String> getNotes() {
|
||||||
|
PersistentClientSessionData entity = getData();
|
||||||
|
if (entity.getNotes() == null || entity.getNotes().isEmpty()) return Collections.emptyMap();
|
||||||
|
return entity.getNotes();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> 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<String, String>());
|
||||||
|
}
|
||||||
|
entity.getUserSessionNotes().put(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> 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<String, String>());
|
||||||
|
}
|
||||||
|
|
||||||
|
@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<String> protocolMappers;
|
||||||
|
|
||||||
|
@JsonProperty("roles")
|
||||||
|
private Set<String> roles;
|
||||||
|
|
||||||
|
@JsonProperty("notes")
|
||||||
|
private Map<String, String> notes;
|
||||||
|
|
||||||
|
@JsonProperty("userSessionNotes")
|
||||||
|
private Map<String, String> userSessionNotes;
|
||||||
|
|
||||||
|
@JsonProperty("executionStatus")
|
||||||
|
private Map<String, ClientSessionModel.ExecutionStatus> executionStatus = new HashMap<>();
|
||||||
|
|
||||||
|
@JsonProperty("timestamp")
|
||||||
|
private int timestamp;
|
||||||
|
|
||||||
|
@JsonProperty("action")
|
||||||
|
private String action;
|
||||||
|
|
||||||
|
@JsonProperty("requiredActions")
|
||||||
|
private Set<String> 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<String> getProtocolMappers() {
|
||||||
|
return protocolMappers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProtocolMappers(Set<String> protocolMappers) {
|
||||||
|
this.protocolMappers = protocolMappers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getRoles() {
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoles(Set<String> roles) {
|
||||||
|
this.roles = roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getNotes() {
|
||||||
|
return notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNotes(Map<String, String> notes) {
|
||||||
|
this.notes = notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getUserSessionNotes() {
|
||||||
|
return userSessionNotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserSessionNotes(Map<String, String> userSessionNotes) {
|
||||||
|
this.userSessionNotes = userSessionNotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, ClientSessionModel.ExecutionStatus> getExecutionStatus() {
|
||||||
|
return executionStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExecutionStatus(Map<String, ClientSessionModel.ExecutionStatus> 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<String> getRequiredActions() {
|
||||||
|
return requiredActions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRequiredActions(Set<String> requiredActions) {
|
||||||
|
this.requiredActions = requiredActions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -1,9 +1,9 @@
|
|||||||
package org.keycloak.models;
|
package org.keycloak.models.session;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
public class OfflineClientSessionModel {
|
public class PersistentClientSessionModel {
|
||||||
|
|
||||||
private String clientSessionId;
|
private String clientSessionId;
|
||||||
private String userSessionId;
|
private String userSessionId;
|
||||||
+90
-19
@@ -1,13 +1,14 @@
|
|||||||
package org.keycloak.services.offline;
|
package org.keycloak.models.session;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.codehaus.jackson.annotate.JsonProperty;
|
import org.codehaus.jackson.annotate.JsonProperty;
|
||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
import org.keycloak.models.ModelException;
|
import org.keycloak.models.ModelException;
|
||||||
import org.keycloak.models.OfflineUserSessionModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
@@ -15,23 +16,47 @@ import org.keycloak.util.JsonSerialization;
|
|||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
public class OfflineUserSessionAdapter implements UserSessionModel {
|
public class PersistentUserSessionAdapter implements UserSessionModel {
|
||||||
|
|
||||||
private final OfflineUserSessionModel model;
|
private final PersistentUserSessionModel model;
|
||||||
private final UserModel user;
|
private final UserModel user;
|
||||||
|
private final RealmModel realm;
|
||||||
|
private final List<ClientSessionModel> clientSessions;
|
||||||
|
|
||||||
private OfflineUserSessionData data;
|
private PersistentUserSessionData data;
|
||||||
|
|
||||||
public OfflineUserSessionAdapter(OfflineUserSessionModel model, UserModel user) {
|
public PersistentUserSessionAdapter(UserSessionModel other) {
|
||||||
this.model = model;
|
this.data = new PersistentUserSessionData();
|
||||||
this.user = user;
|
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
|
public PersistentUserSessionAdapter(PersistentUserSessionModel model, RealmModel realm, UserModel user, List<ClientSessionModel> clientSessions) {
|
||||||
private OfflineUserSessionData getData() {
|
this.model = model;
|
||||||
|
this.realm = realm;
|
||||||
|
this.user = user;
|
||||||
|
this.clientSessions = clientSessions;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lazily init data
|
||||||
|
private PersistentUserSessionData getData() {
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
try {
|
try {
|
||||||
data = JsonSerialization.readValue(model.getData(), OfflineUserSessionData.class);
|
data = JsonSerialization.readValue(model.getData(), PersistentUserSessionData.class);
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
throw new ModelException(ioe);
|
throw new ModelException(ioe);
|
||||||
}
|
}
|
||||||
@@ -40,6 +65,18 @@ public class OfflineUserSessionAdapter implements UserSessionModel {
|
|||||||
return data;
|
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
|
@Override
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return model.getUserSessionId();
|
return model.getUserSessionId();
|
||||||
@@ -60,6 +97,11 @@ public class OfflineUserSessionAdapter implements UserSessionModel {
|
|||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RealmModel getRealm() {
|
||||||
|
return realm;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getLoginUsername() {
|
public String getLoginUsername() {
|
||||||
return user.getUsername();
|
return user.getUsername();
|
||||||
@@ -87,17 +129,17 @@ public class OfflineUserSessionAdapter implements UserSessionModel {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getLastSessionRefresh() {
|
public int getLastSessionRefresh() {
|
||||||
return 0;
|
return model.getLastSessionRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setLastSessionRefresh(int seconds) {
|
public void setLastSessionRefresh(int seconds) {
|
||||||
// Ignore
|
model.setLastSessionRefresh(seconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ClientSessionModel> getClientSessions() {
|
public List<ClientSessionModel> getClientSessions() {
|
||||||
throw new IllegalStateException("Not yet supported");
|
return clientSessions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -107,13 +149,19 @@ public class OfflineUserSessionAdapter implements UserSessionModel {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setNote(String name, String value) {
|
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<String, String>());
|
||||||
|
}
|
||||||
|
data.getNotes().put(name, value);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeNote(String name) {
|
public void removeNote(String name) {
|
||||||
throw new IllegalStateException("Illegal to remove note from offline session");
|
if (getData().getNotes() != null) {
|
||||||
|
getData().getNotes().remove(name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -123,16 +171,29 @@ public class OfflineUserSessionAdapter implements UserSessionModel {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public State getState() {
|
public State getState() {
|
||||||
return null;
|
return getData().getState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setState(State state) {
|
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")
|
@JsonProperty("brokerSessionId")
|
||||||
private String brokerSessionId;
|
private String brokerSessionId;
|
||||||
@@ -155,6 +216,9 @@ public class OfflineUserSessionAdapter implements UserSessionModel {
|
|||||||
@JsonProperty("notes")
|
@JsonProperty("notes")
|
||||||
private Map<String, String> notes;
|
private Map<String, String> notes;
|
||||||
|
|
||||||
|
@JsonProperty("state")
|
||||||
|
private State state;
|
||||||
|
|
||||||
public String getBrokerSessionId() {
|
public String getBrokerSessionId() {
|
||||||
return brokerSessionId;
|
return brokerSessionId;
|
||||||
}
|
}
|
||||||
@@ -211,5 +275,12 @@ public class OfflineUserSessionAdapter implements UserSessionModel {
|
|||||||
this.notes = notes;
|
this.notes = notes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public State getState() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setState(State state) {
|
||||||
|
this.state = state;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+13
-2
@@ -1,11 +1,13 @@
|
|||||||
package org.keycloak.models;
|
package org.keycloak.models.session;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
public class OfflineUserSessionModel {
|
public class PersistentUserSessionModel {
|
||||||
|
|
||||||
private String userSessionId;
|
private String userSessionId;
|
||||||
|
private int lastSessionRefresh;
|
||||||
|
|
||||||
private String data;
|
private String data;
|
||||||
|
|
||||||
public String getUserSessionId() {
|
public String getUserSessionId() {
|
||||||
@@ -16,6 +18,15 @@ public class OfflineUserSessionModel {
|
|||||||
this.userSessionId = userSessionId;
|
this.userSessionId = userSessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getLastSessionRefresh() {
|
||||||
|
return lastSessionRefresh;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastSessionRefresh(int lastSessionRefresh) {
|
||||||
|
this.lastSessionRefresh = lastSessionRefresh;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public String getData() {
|
public String getData() {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
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<UserSessionModel> loadUserSessions(int firstResult, int maxResults, boolean offline);
|
||||||
|
|
||||||
|
int getUserSessionsCount(boolean offline);
|
||||||
|
|
||||||
|
}
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
package org.keycloak.models.session;
|
||||||
|
|
||||||
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public interface UserSessionPersisterProviderFactory extends ProviderFactory<UserSessionPersisterProvider> {
|
||||||
|
}
|
||||||
@@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,8 +10,8 @@ import org.keycloak.models.IdentityProviderMapperModel;
|
|||||||
import org.keycloak.models.IdentityProviderModel;
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
import org.keycloak.models.ModelException;
|
import org.keycloak.models.ModelException;
|
||||||
import org.keycloak.models.OTPPolicy;
|
import org.keycloak.models.OTPPolicy;
|
||||||
import org.keycloak.models.OfflineClientSessionModel;
|
import org.keycloak.models.session.PersistentClientSessionModel;
|
||||||
import org.keycloak.models.OfflineUserSessionModel;
|
import org.keycloak.models.session.PersistentUserSessionModel;
|
||||||
import org.keycloak.models.ProtocolMapperModel;
|
import org.keycloak.models.ProtocolMapperModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RequiredActionProviderModel;
|
import org.keycloak.models.RequiredActionProviderModel;
|
||||||
@@ -250,11 +250,11 @@ public class ModelToRepresentation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (realm.getEventsListeners() != null) {
|
if (realm.getEventsListeners() != null) {
|
||||||
rep.setEventsListeners(new LinkedList<String>(realm.getEventsListeners()));
|
rep.setEventsListeners(new LinkedList<>(realm.getEventsListeners()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(realm.getEnabledEventTypes() != null) {
|
if(realm.getEnabledEventTypes() != null) {
|
||||||
rep.setEnabledEventTypes(new LinkedList<String>(realm.getEnabledEventTypes()));
|
rep.setEnabledEventTypes(new LinkedList<>(realm.getEnabledEventTypes()));
|
||||||
}
|
}
|
||||||
|
|
||||||
rep.setAdminEventsEnabled(realm.isAdminEventsEnabled());
|
rep.setAdminEventsEnabled(realm.isAdminEventsEnabled());
|
||||||
@@ -511,13 +511,13 @@ public class ModelToRepresentation {
|
|||||||
return rep;
|
return rep;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static OfflineUserSessionRepresentation toRepresentation(RealmModel realm, OfflineUserSessionModel model, Collection<OfflineClientSessionModel> clientSessions) {
|
public static OfflineUserSessionRepresentation toRepresentation(RealmModel realm, PersistentUserSessionModel model, Collection<PersistentClientSessionModel> clientSessions) {
|
||||||
OfflineUserSessionRepresentation rep = new OfflineUserSessionRepresentation();
|
OfflineUserSessionRepresentation rep = new OfflineUserSessionRepresentation();
|
||||||
rep.setData(model.getData());
|
rep.setData(model.getData());
|
||||||
rep.setUserSessionId(model.getUserSessionId());
|
rep.setUserSessionId(model.getUserSessionId());
|
||||||
|
|
||||||
List<OfflineClientSessionRepresentation> clientSessionReps = new LinkedList<>();
|
List<OfflineClientSessionRepresentation> clientSessionReps = new LinkedList<>();
|
||||||
for (OfflineClientSessionModel clsm : clientSessions) {
|
for (PersistentClientSessionModel clsm : clientSessions) {
|
||||||
OfflineClientSessionRepresentation clrep = toRepresentation(realm, clsm);
|
OfflineClientSessionRepresentation clrep = toRepresentation(realm, clsm);
|
||||||
clientSessionReps.add(clrep);
|
clientSessionReps.add(clrep);
|
||||||
}
|
}
|
||||||
@@ -525,7 +525,7 @@ public class ModelToRepresentation {
|
|||||||
return rep;
|
return rep;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static OfflineClientSessionRepresentation toRepresentation(RealmModel realm, OfflineClientSessionModel model) {
|
public static OfflineClientSessionRepresentation toRepresentation(RealmModel realm, PersistentClientSessionModel model) {
|
||||||
OfflineClientSessionRepresentation rep = new OfflineClientSessionRepresentation();
|
OfflineClientSessionRepresentation rep = new OfflineClientSessionRepresentation();
|
||||||
|
|
||||||
String clientInternalId = model.getClientId();
|
String clientInternalId = model.getClientId();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package org.keycloak.models.utils;
|
package org.keycloak.models.utils;
|
||||||
|
|
||||||
import org.keycloak.models.OfflineClientSessionModel;
|
import org.keycloak.models.session.PersistentClientSessionModel;
|
||||||
import org.keycloak.models.OfflineUserSessionModel;
|
import org.keycloak.models.session.PersistentUserSessionModel;
|
||||||
import org.keycloak.representations.idm.OfflineClientSessionRepresentation;
|
import org.keycloak.representations.idm.OfflineClientSessionRepresentation;
|
||||||
import org.keycloak.representations.idm.OfflineUserSessionRepresentation;
|
import org.keycloak.representations.idm.OfflineUserSessionRepresentation;
|
||||||
import org.keycloak.util.Base64;
|
import org.keycloak.util.Base64;
|
||||||
@@ -985,11 +985,6 @@ public class RepresentationToModel {
|
|||||||
user.addConsent(consentModel);
|
user.addConsent(consentModel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (userRep.getOfflineUserSessions() != null) {
|
|
||||||
for (OfflineUserSessionRepresentation sessionRep : userRep.getOfflineUserSessions()) {
|
|
||||||
importOfflineSession(session, newRealm, user, sessionRep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (userRep.getServiceAccountClientId() != null) {
|
if (userRep.getServiceAccountClientId() != null) {
|
||||||
String clientId = userRep.getServiceAccountClientId();
|
String clientId = userRep.getServiceAccountClientId();
|
||||||
ClientModel client = clientMap.get(clientId);
|
ClientModel client = clientMap.get(clientId);
|
||||||
@@ -1160,28 +1155,29 @@ public class RepresentationToModel {
|
|||||||
return consentModel;
|
return consentModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void importOfflineSession(KeycloakSession session, RealmModel newRealm, UserModel user, OfflineUserSessionRepresentation sessionRep) {
|
// TODO
|
||||||
OfflineUserSessionModel model = new OfflineUserSessionModel();
|
// public static void importOfflineSession(KeycloakSession session, RealmModel newRealm, UserModel user, OfflineUserSessionRepresentation sessionRep) {
|
||||||
model.setUserSessionId(sessionRep.getUserSessionId());
|
// PersistentUserSessionModel model = new PersistentUserSessionModel();
|
||||||
model.setData(sessionRep.getData());
|
// model.setUserSessionId(sessionRep.getUserSessionId());
|
||||||
session.users().addOfflineUserSession(newRealm, user, model);
|
// model.setData(sessionRep.getData());
|
||||||
|
// session.users().createOfflineUserSession(newRealm, user, model);
|
||||||
for (OfflineClientSessionRepresentation csRep : sessionRep.getOfflineClientSessions()) {
|
//
|
||||||
OfflineClientSessionModel csModel = new OfflineClientSessionModel();
|
// for (OfflineClientSessionRepresentation csRep : sessionRep.getOfflineClientSessions()) {
|
||||||
String clientId = csRep.getClient();
|
// PersistentClientSessionModel csModel = new PersistentClientSessionModel();
|
||||||
ClientModel client = newRealm.getClientByClientId(clientId);
|
// String clientId = csRep.getClient();
|
||||||
if (client == null) {
|
// ClientModel client = newRealm.getClientByClientId(clientId);
|
||||||
throw new RuntimeException("Unable to find client " + clientId + " referenced from offlineClientSession of user " + user.getUsername());
|
// 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.setClientId(client.getId());
|
||||||
csModel.setClientSessionId(csRep.getClientSessionId());
|
// csModel.setUserId(user.getId());
|
||||||
csModel.setUserSessionId(sessionRep.getUserSessionId());
|
// csModel.setClientSessionId(csRep.getClientSessionId());
|
||||||
csModel.setData(csRep.getData());
|
// csModel.setUserSessionId(sessionRep.getUserSessionId());
|
||||||
|
// csModel.setData(csRep.getData());
|
||||||
session.users().addOfflineClientSession(newRealm, csModel);
|
//
|
||||||
}
|
// session.users().createOfflineClientSession(newRealm, csModel);
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
public static AuthenticationFlowModel toModel(AuthenticationFlowRepresentation rep) {
|
public static AuthenticationFlowModel toModel(AuthenticationFlowRepresentation rep) {
|
||||||
AuthenticationFlowModel model = new AuthenticationFlowModel();
|
AuthenticationFlowModel model = new AuthenticationFlowModel();
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
package org.keycloak.models.utils;
|
package org.keycloak.models.utils;
|
||||||
|
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.OfflineClientSessionModel;
|
|
||||||
import org.keycloak.models.OfflineUserSessionModel;
|
|
||||||
import org.keycloak.models.UserConsentModel;
|
import org.keycloak.models.UserConsentModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserCredentialValueModel;
|
import org.keycloak.models.UserCredentialValueModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
org.keycloak.models.session.DisabledUserSessionPersisterProvider
|
||||||
@@ -3,4 +3,5 @@ org.keycloak.mappers.UserFederationMapperSpi
|
|||||||
org.keycloak.models.RealmSpi
|
org.keycloak.models.RealmSpi
|
||||||
org.keycloak.models.UserSessionSpi
|
org.keycloak.models.UserSessionSpi
|
||||||
org.keycloak.models.UserSpi
|
org.keycloak.models.UserSpi
|
||||||
|
org.keycloak.models.session.UserSessionPersisterSpi
|
||||||
org.keycloak.migration.MigrationSpi
|
org.keycloak.migration.MigrationSpi
|
||||||
@@ -24,8 +24,8 @@ import org.keycloak.models.FederatedIdentityModel;
|
|||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.ModelDuplicateException;
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
import org.keycloak.models.ModelException;
|
import org.keycloak.models.ModelException;
|
||||||
import org.keycloak.models.OfflineClientSessionModel;
|
import org.keycloak.models.session.PersistentClientSessionModel;
|
||||||
import org.keycloak.models.OfflineUserSessionModel;
|
import org.keycloak.models.session.PersistentUserSessionModel;
|
||||||
import org.keycloak.models.ProtocolMapperModel;
|
import org.keycloak.models.ProtocolMapperModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RequiredActionProviderModel;
|
import org.keycloak.models.RequiredActionProviderModel;
|
||||||
@@ -35,8 +35,8 @@ import org.keycloak.models.UserFederationProviderModel;
|
|||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserProvider;
|
import org.keycloak.models.UserProvider;
|
||||||
import org.keycloak.models.entities.FederatedIdentityEntity;
|
import org.keycloak.models.entities.FederatedIdentityEntity;
|
||||||
import org.keycloak.models.entities.OfflineClientSessionEntity;
|
import org.keycloak.models.entities.PersistentClientSessionEntity;
|
||||||
import org.keycloak.models.entities.OfflineUserSessionEntity;
|
import org.keycloak.models.entities.PersistentUserSessionEntity;
|
||||||
import org.keycloak.models.entities.UserEntity;
|
import org.keycloak.models.entities.UserEntity;
|
||||||
import org.keycloak.models.file.adapter.UserAdapter;
|
import org.keycloak.models.file.adapter.UserAdapter;
|
||||||
import org.keycloak.models.utils.CredentialValidation;
|
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.
|
//throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
|
||||||
return null; // not supported yet
|
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<OfflineUserSessionEntity>());
|
|
||||||
}
|
|
||||||
|
|
||||||
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<OfflineClientSessionEntity>());
|
|
||||||
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<OfflineUserSessionModel> getOfflineUserSessions(RealmModel realm, UserModel userModel) {
|
|
||||||
userModel = getUserById(userModel.getId(), realm);
|
|
||||||
UserEntity user = ((UserAdapter) userModel).getUserEntity();
|
|
||||||
|
|
||||||
if (user.getOfflineUserSessions()==null) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
} else {
|
|
||||||
List<OfflineUserSessionModel> 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<OfflineClientSessionModel> getOfflineClientSessions(RealmModel realm, UserModel userModel) {
|
|
||||||
userModel = getUserById(userModel.getId(), realm);
|
|
||||||
UserEntity user = ((UserAdapter) userModel).getUserEntity();
|
|
||||||
|
|
||||||
List<OfflineClientSessionModel> 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<OfflineClientSessionModel> getOfflineClientSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
|
|
||||||
List<OfflineClientSessionModel> result = new LinkedList<>();
|
|
||||||
|
|
||||||
List<UserModel> 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -22,10 +22,7 @@ import org.keycloak.models.ClientModel;
|
|||||||
import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt;
|
import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt;
|
||||||
|
|
||||||
import org.keycloak.models.ModelDuplicateException;
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
import org.keycloak.models.ModelException;
|
|
||||||
import org.keycloak.models.OTPPolicy;
|
import org.keycloak.models.OTPPolicy;
|
||||||
import org.keycloak.models.OfflineClientSessionModel;
|
|
||||||
import org.keycloak.models.OfflineUserSessionModel;
|
|
||||||
import org.keycloak.models.UserConsentModel;
|
import org.keycloak.models.UserConsentModel;
|
||||||
import org.keycloak.models.PasswordPolicy;
|
import org.keycloak.models.PasswordPolicy;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
@@ -35,8 +32,6 @@ import org.keycloak.models.UserCredentialValueModel;
|
|||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.entities.CredentialEntity;
|
import org.keycloak.models.entities.CredentialEntity;
|
||||||
import org.keycloak.models.entities.FederatedIdentityEntity;
|
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.RoleEntity;
|
||||||
import org.keycloak.models.entities.UserEntity;
|
import org.keycloak.models.entities.UserEntity;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
@@ -44,7 +39,6 @@ import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
|
|||||||
import org.keycloak.util.Time;
|
import org.keycloak.util.Time;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -53,8 +47,6 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UserModel for JSON persistence.
|
* UserModel for JSON persistence.
|
||||||
*
|
*
|
||||||
|
|||||||
+5
-93
@@ -4,6 +4,8 @@ import org.keycloak.models.*;
|
|||||||
import org.keycloak.models.cache.CacheUserProvider;
|
import org.keycloak.models.cache.CacheUserProvider;
|
||||||
import org.keycloak.models.cache.UserCache;
|
import org.keycloak.models.cache.UserCache;
|
||||||
import org.keycloak.models.cache.entities.CachedUser;
|
import org.keycloak.models.cache.entities.CachedUser;
|
||||||
|
import org.keycloak.models.session.PersistentClientSessionModel;
|
||||||
|
import org.keycloak.models.session.PersistentUserSessionModel;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@@ -121,7 +123,7 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
|
|||||||
if (model == null) return null;
|
if (model == null) return null;
|
||||||
if (managedUsers.containsKey(id)) return managedUsers.get(id);
|
if (managedUsers.containsKey(id)) return managedUsers.get(id);
|
||||||
if (userInvalidations.containsKey(id)) return model;
|
if (userInvalidations.containsKey(id)) return model;
|
||||||
cached = new CachedUser(this, realm, model);
|
cached = new CachedUser(realm, model);
|
||||||
cache.addCachedUser(realm.getId(), cached);
|
cache.addCachedUser(realm.getId(), cached);
|
||||||
} else if (managedUsers.containsKey(id)) {
|
} else if (managedUsers.containsKey(id)) {
|
||||||
return managedUsers.get(id);
|
return managedUsers.get(id);
|
||||||
@@ -146,7 +148,7 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
|
|||||||
if (model == null) return null;
|
if (model == null) return null;
|
||||||
if (managedUsers.containsKey(model.getId())) return managedUsers.get(model.getId());
|
if (managedUsers.containsKey(model.getId())) return managedUsers.get(model.getId());
|
||||||
if (userInvalidations.containsKey(model.getId())) return model;
|
if (userInvalidations.containsKey(model.getId())) return model;
|
||||||
cached = new CachedUser(this, realm, model);
|
cached = new CachedUser(realm, model);
|
||||||
cache.addCachedUser(realm.getId(), cached);
|
cache.addCachedUser(realm.getId(), cached);
|
||||||
} else if (userInvalidations.containsKey(cached.getId())) {
|
} else if (userInvalidations.containsKey(cached.getId())) {
|
||||||
return getDelegate().getUserById(cached.getId(), realm);
|
return getDelegate().getUserById(cached.getId(), realm);
|
||||||
@@ -173,7 +175,7 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
|
|||||||
UserModel model = getDelegate().getUserByEmail(email, realm);
|
UserModel model = getDelegate().getUserByEmail(email, realm);
|
||||||
if (model == null) return null;
|
if (model == null) return null;
|
||||||
if (userInvalidations.containsKey(model.getId())) return model;
|
if (userInvalidations.containsKey(model.getId())) return model;
|
||||||
cached = new CachedUser(this, realm, model);
|
cached = new CachedUser(realm, model);
|
||||||
cache.addCachedUser(realm.getId(), cached);
|
cache.addCachedUser(realm.getId(), cached);
|
||||||
} else if (userInvalidations.containsKey(cached.getId())) {
|
} else if (userInvalidations.containsKey(cached.getId())) {
|
||||||
return getDelegate().getUserByEmail(email, realm);
|
return getDelegate().getUserByEmail(email, realm);
|
||||||
@@ -328,94 +330,4 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
|
|||||||
public void preRemove(ClientModel client, ProtocolMapperModel protocolMapper) {
|
public void preRemove(ClientModel client, ProtocolMapperModel protocolMapper) {
|
||||||
getDelegate().preRemove(client, 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<OfflineUserSessionModel> 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<OfflineClientSessionModel> 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<OfflineClientSessionModel> getOfflineClientSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
|
|
||||||
return getDelegate().getOfflineClientSessions(realm, client, firstResult, maxResults);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+3
-24
@@ -1,20 +1,15 @@
|
|||||||
package org.keycloak.models.cache.entities;
|
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.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserCredentialValueModel;
|
import org.keycloak.models.UserCredentialValueModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.cache.CacheUserProvider;
|
|
||||||
import org.keycloak.util.MultivaluedHashMap;
|
import org.keycloak.util.MultivaluedHashMap;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -30,18 +25,16 @@ public class CachedUser implements Serializable {
|
|||||||
private String lastName;
|
private String lastName;
|
||||||
private String email;
|
private String email;
|
||||||
private boolean emailVerified;
|
private boolean emailVerified;
|
||||||
private List<UserCredentialValueModel> credentials = new LinkedList<UserCredentialValueModel>();
|
private List<UserCredentialValueModel> credentials = new LinkedList<>();
|
||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
private boolean totp;
|
private boolean totp;
|
||||||
private String federationLink;
|
private String federationLink;
|
||||||
private String serviceAccountClientLink;
|
private String serviceAccountClientLink;
|
||||||
private MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
|
private MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
|
||||||
private Set<String> requiredActions = new HashSet<>();
|
private Set<String> requiredActions = new HashSet<>();
|
||||||
private Set<String> roleMappings = new HashSet<String>();
|
private Set<String> roleMappings = new HashSet<>();
|
||||||
private Map<String, OfflineUserSessionModel> offlineUserSessions = new HashMap<>();
|
|
||||||
private Map<String, OfflineClientSessionModel> offlineClientSessions = new HashMap<>();
|
|
||||||
|
|
||||||
public CachedUser(CacheUserProvider cacheUserProvider, RealmModel realm, UserModel user) {
|
public CachedUser(RealmModel realm, UserModel user) {
|
||||||
this.id = user.getId();
|
this.id = user.getId();
|
||||||
this.realm = realm.getId();
|
this.realm = realm.getId();
|
||||||
this.username = user.getUsername();
|
this.username = user.getUsername();
|
||||||
@@ -60,12 +53,6 @@ public class CachedUser implements Serializable {
|
|||||||
for (RoleModel role : user.getRoleMappings()) {
|
for (RoleModel role : user.getRoleMappings()) {
|
||||||
roleMappings.add(role.getId());
|
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() {
|
public String getId() {
|
||||||
@@ -131,12 +118,4 @@ public class CachedUser implements Serializable {
|
|||||||
public String getServiceAccountClientLink() {
|
public String getServiceAccountClientLink() {
|
||||||
return serviceAccountClientLink;
|
return serviceAccountClientLink;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, OfflineUserSessionModel> getOfflineUserSessions() {
|
|
||||||
return offlineUserSessions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, OfflineClientSessionModel> getOfflineClientSessions() {
|
|
||||||
return offlineClientSessions;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
package org.keycloak.models.jpa;
|
|
||||||
|
|
||||||
import org.keycloak.models.KeycloakTransaction;
|
|
||||||
|
|
||||||
import javax.persistence.EntityManager;
|
|
||||||
import javax.persistence.PersistenceException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,8 +4,6 @@ import org.keycloak.models.ClientModel;
|
|||||||
import org.keycloak.models.CredentialValidationOutput;
|
import org.keycloak.models.CredentialValidationOutput;
|
||||||
import org.keycloak.models.FederatedIdentityModel;
|
import org.keycloak.models.FederatedIdentityModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.OfflineClientSessionModel;
|
|
||||||
import org.keycloak.models.OfflineUserSessionModel;
|
|
||||||
import org.keycloak.models.ProtocolMapperModel;
|
import org.keycloak.models.ProtocolMapperModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RequiredActionProviderModel;
|
import org.keycloak.models.RequiredActionProviderModel;
|
||||||
@@ -15,21 +13,15 @@ import org.keycloak.models.UserFederationProviderModel;
|
|||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserProvider;
|
import org.keycloak.models.UserProvider;
|
||||||
import org.keycloak.models.jpa.entities.FederatedIdentityEntity;
|
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.UserAttributeEntity;
|
||||||
import org.keycloak.models.jpa.entities.UserEntity;
|
import org.keycloak.models.jpa.entities.UserEntity;
|
||||||
import org.keycloak.models.utils.CredentialValidation;
|
import org.keycloak.models.utils.CredentialValidation;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
|
||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
import javax.persistence.Query;
|
|
||||||
import javax.persistence.TypedQuery;
|
import javax.persistence.TypedQuery;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -175,10 +167,6 @@ public class JpaUserProvider implements UserProvider {
|
|||||||
.setParameter("realmId", realm.getId()).executeUpdate();
|
.setParameter("realmId", realm.getId()).executeUpdate();
|
||||||
num = em.createNamedQuery("deleteUserAttributesByRealm")
|
num = em.createNamedQuery("deleteUserAttributesByRealm")
|
||||||
.setParameter("realmId", realm.getId()).executeUpdate();
|
.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")
|
num = em.createNamedQuery("deleteUsersByRealm")
|
||||||
.setParameter("realmId", realm.getId()).executeUpdate();
|
.setParameter("realmId", realm.getId()).executeUpdate();
|
||||||
}
|
}
|
||||||
@@ -205,14 +193,6 @@ public class JpaUserProvider implements UserProvider {
|
|||||||
.setParameter("realmId", realm.getId())
|
.setParameter("realmId", realm.getId())
|
||||||
.setParameter("link", link.getId())
|
.setParameter("link", link.getId())
|
||||||
.executeUpdate();
|
.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")
|
num = em.createNamedQuery("deleteUsersByRealmAndLink")
|
||||||
.setParameter("realmId", realm.getId())
|
.setParameter("realmId", realm.getId())
|
||||||
.setParameter("link", link.getId())
|
.setParameter("link", link.getId())
|
||||||
@@ -230,8 +210,6 @@ public class JpaUserProvider implements UserProvider {
|
|||||||
em.createNamedQuery("deleteUserConsentProtMappersByClient").setParameter("clientId", client.getId()).executeUpdate();
|
em.createNamedQuery("deleteUserConsentProtMappersByClient").setParameter("clientId", client.getId()).executeUpdate();
|
||||||
em.createNamedQuery("deleteUserConsentRolesByClient").setParameter("clientId", client.getId()).executeUpdate();
|
em.createNamedQuery("deleteUserConsentRolesByClient").setParameter("clientId", client.getId()).executeUpdate();
|
||||||
em.createNamedQuery("deleteUserConsentsByClient").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
|
@Override
|
||||||
@@ -479,167 +457,4 @@ public class JpaUserProvider implements UserProvider {
|
|||||||
// Not supported yet
|
// Not supported yet
|
||||||
return null;
|
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<OfflineUserSessionModel> getOfflineUserSessions(RealmModel realm, UserModel user) {
|
|
||||||
UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
|
|
||||||
|
|
||||||
List<OfflineUserSessionModel> 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<OfflineClientSessionModel> getOfflineClientSessions(RealmModel realm, UserModel user) {
|
|
||||||
UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
|
|
||||||
|
|
||||||
List<OfflineClientSessionModel> 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<OfflineClientSessionModel> getOfflineClientSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
|
|
||||||
TypedQuery<OfflineClientSessionEntity> query = em.createNamedQuery("findOfflineClientSessionsByClient", OfflineClientSessionEntity.class);
|
|
||||||
query.setParameter("clientId", client.getId());
|
|
||||||
|
|
||||||
if (firstResult != -1) {
|
|
||||||
query.setFirstResult(firstResult);
|
|
||||||
}
|
|
||||||
if (maxResults != -1) {
|
|
||||||
query.setMaxResults(maxResults);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<OfflineClientSessionEntity> results = query.getResultList();
|
|
||||||
Set<OfflineClientSessionModel> set = new HashSet<>();
|
|
||||||
for (OfflineClientSessionEntity entity : results) {
|
|
||||||
set.add(toModel(entity));
|
|
||||||
}
|
|
||||||
return set;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -2,8 +2,6 @@ package org.keycloak.models.jpa;
|
|||||||
|
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.OTPPolicy;
|
import org.keycloak.models.OTPPolicy;
|
||||||
import org.keycloak.models.OfflineClientSessionModel;
|
|
||||||
import org.keycloak.models.OfflineUserSessionModel;
|
|
||||||
import org.keycloak.models.ProtocolMapperModel;
|
import org.keycloak.models.ProtocolMapperModel;
|
||||||
import org.keycloak.models.UserConsentModel;
|
import org.keycloak.models.UserConsentModel;
|
||||||
import org.keycloak.models.ModelDuplicateException;
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
@@ -16,8 +14,6 @@ import org.keycloak.models.UserCredentialModel;
|
|||||||
import org.keycloak.models.UserCredentialValueModel;
|
import org.keycloak.models.UserCredentialValueModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.jpa.entities.CredentialEntity;
|
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.UserConsentEntity;
|
||||||
import org.keycloak.models.jpa.entities.UserConsentProtocolMapperEntity;
|
import org.keycloak.models.jpa.entities.UserConsentProtocolMapperEntity;
|
||||||
import org.keycloak.models.jpa.entities.UserConsentRoleEntity;
|
import org.keycloak.models.jpa.entities.UserConsentRoleEntity;
|
||||||
@@ -37,11 +33,8 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|||||||
-83
@@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
@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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-59
@@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
@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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,13 +3,9 @@ package org.keycloak.models.jpa.entities;
|
|||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
|
||||||
import javax.persistence.CascadeType;
|
import javax.persistence.CascadeType;
|
||||||
import javax.persistence.CollectionTable;
|
|
||||||
import javax.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import javax.persistence.ElementCollection;
|
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
import javax.persistence.Id;
|
import javax.persistence.Id;
|
||||||
import javax.persistence.JoinColumn;
|
|
||||||
import javax.persistence.MapKeyColumn;
|
|
||||||
import javax.persistence.NamedQueries;
|
import javax.persistence.NamedQueries;
|
||||||
import javax.persistence.NamedQuery;
|
import javax.persistence.NamedQuery;
|
||||||
import javax.persistence.OneToMany;
|
import javax.persistence.OneToMany;
|
||||||
@@ -18,8 +14,6 @@ import javax.persistence.UniqueConstraint;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
@@ -89,12 +83,6 @@ public class UserEntity {
|
|||||||
@Column(name="SERVICE_ACCOUNT_CLIENT_LINK")
|
@Column(name="SERVICE_ACCOUNT_CLIENT_LINK")
|
||||||
protected String serviceAccountClientLink;
|
protected String serviceAccountClientLink;
|
||||||
|
|
||||||
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="user")
|
|
||||||
protected Collection<OfflineUserSessionEntity> offlineUserSessions = new ArrayList<>();
|
|
||||||
|
|
||||||
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="user")
|
|
||||||
protected Collection<OfflineClientSessionEntity> offlineClientSessions = new ArrayList<>();
|
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
@@ -224,22 +212,6 @@ public class UserEntity {
|
|||||||
this.serviceAccountClientLink = serviceAccountClientLink;
|
this.serviceAccountClientLink = serviceAccountClientLink;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<OfflineUserSessionEntity> getOfflineUserSessions() {
|
|
||||||
return offlineUserSessions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOfflineUserSessions(Collection<OfflineUserSessionEntity> offlineUserSessions) {
|
|
||||||
this.offlineUserSessions = offlineUserSessions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Collection<OfflineClientSessionEntity> getOfflineClientSessions() {
|
|
||||||
return offlineClientSessions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOfflineClientSessions(Collection<OfflineClientSessionEntity> offlineClientSessions) {
|
|
||||||
this.offlineClientSessions = offlineClientSessions;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
|||||||
+239
@@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
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<PersistentClientSessionEntity> 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<PersistentClientSessionEntity> getClientSessionsByUserSession(String userSessionId, boolean offline) {
|
||||||
|
TypedQuery<PersistentClientSessionEntity> 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<UserSessionModel> loadUserSessions(int firstResult, int maxResults, boolean offline) {
|
||||||
|
TypedQuery<PersistentUserSessionEntity> query = em.createNamedQuery("findUserSessions", PersistentUserSessionEntity.class);
|
||||||
|
query.setParameter("offline", offline);
|
||||||
|
|
||||||
|
if (firstResult != -1) {
|
||||||
|
query.setFirstResult(firstResult);
|
||||||
|
}
|
||||||
|
if (maxResults != -1) {
|
||||||
|
query.setMaxResults(maxResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<PersistentUserSessionEntity> results = query.getResultList();
|
||||||
|
List<UserSessionModel> result = new ArrayList<>();
|
||||||
|
List<String> userSessionIds = new ArrayList<>();
|
||||||
|
for (PersistentUserSessionEntity entity : results) {
|
||||||
|
result.add(toAdapter(entity));
|
||||||
|
userSessionIds.add(entity.getUserSessionId());
|
||||||
|
}
|
||||||
|
|
||||||
|
TypedQuery<PersistentClientSessionEntity> query2 = em.createNamedQuery("findClientSessionsByUserSessions", PersistentClientSessionEntity.class);
|
||||||
|
query2.setParameter("userSessionIds", userSessionIds);
|
||||||
|
query2.setParameter("offline", offline);
|
||||||
|
List<PersistentClientSessionEntity> clientSessions = query2.getResultList();
|
||||||
|
|
||||||
|
// Assume both userSessions and clientSessions ordered by userSessionId
|
||||||
|
int j=0;
|
||||||
|
for (UserSessionModel ss : result) {
|
||||||
|
PersistentUserSessionAdapter userSession = (PersistentUserSessionAdapter) ss;
|
||||||
|
List<ClientSessionModel> currentClientSessions = userSession.getClientSessions(); // This is empty now and we want to fill it
|
||||||
|
|
||||||
|
boolean next = true;
|
||||||
|
while (next && j<clientSessions.size()) {
|
||||||
|
PersistentClientSessionEntity clientSession = clientSessions.get(j);
|
||||||
|
if (clientSession.getUserSessionId().equals(userSession.getId())) {
|
||||||
|
PersistentClientSessionAdapter clientSessAdapter = toAdapter(userSession.getRealm(), userSession, clientSession);
|
||||||
|
currentClientSessions.add(clientSessAdapter);
|
||||||
|
j++;
|
||||||
|
} else {
|
||||||
|
next = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PersistentUserSessionAdapter toAdapter(PersistentUserSessionEntity entity) {
|
||||||
|
RealmModel realm = session.realms().getRealm(entity.getRealmId());
|
||||||
|
UserModel user = session.users().getUserById(entity.getUserId(), realm);
|
||||||
|
|
||||||
|
PersistentUserSessionModel model = new PersistentUserSessionModel();
|
||||||
|
model.setUserSessionId(entity.getUserSessionId());
|
||||||
|
model.setLastSessionRefresh(entity.getLastSessionRefresh());
|
||||||
|
model.setData(entity.getData());
|
||||||
|
|
||||||
|
List<ClientSessionModel> 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() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
+44
@@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+132
@@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+147
@@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
org.keycloak.models.jpa.session.JpaUserSessionPersisterProviderFactory
|
||||||
-239
@@ -10,10 +10,6 @@ import org.keycloak.models.ClientModel;
|
|||||||
import org.keycloak.models.CredentialValidationOutput;
|
import org.keycloak.models.CredentialValidationOutput;
|
||||||
import org.keycloak.models.FederatedIdentityModel;
|
import org.keycloak.models.FederatedIdentityModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
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.ProtocolMapperModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RequiredActionProviderModel;
|
import org.keycloak.models.RequiredActionProviderModel;
|
||||||
@@ -23,17 +19,13 @@ import org.keycloak.models.UserFederationProviderModel;
|
|||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserProvider;
|
import org.keycloak.models.UserProvider;
|
||||||
import org.keycloak.models.entities.FederatedIdentityEntity;
|
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.MongoUserConsentEntity;
|
||||||
import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
|
import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
|
||||||
import org.keycloak.models.utils.CredentialValidation;
|
import org.keycloak.models.utils.CredentialValidation;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -407,36 +399,6 @@ public class MongoUserProvider implements UserProvider {
|
|||||||
.and("clientId").is(client.getId())
|
.and("clientId").is(client.getId())
|
||||||
.get();
|
.get();
|
||||||
getMongoStore().removeEntities(MongoUserConsentEntity.class, query, false, invocationContext);
|
getMongoStore().removeEntities(MongoUserConsentEntity.class, query, false, invocationContext);
|
||||||
|
|
||||||
// Remove all offlineClientSessions
|
|
||||||
query = new QueryBuilder()
|
|
||||||
.and("offlineUserSessions.offlineClientSessions.clientId").is(client.getId())
|
|
||||||
.get();
|
|
||||||
List<MongoUserEntity> 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
|
@Override
|
||||||
@@ -482,205 +444,4 @@ public class MongoUserProvider implements UserProvider {
|
|||||||
// Not supported yet
|
// Not supported yet
|
||||||
return null;
|
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<OfflineUserSessionEntity>());
|
|
||||||
}
|
|
||||||
|
|
||||||
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<OfflineClientSessionEntity>());
|
|
||||||
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<OfflineUserSessionModel> getOfflineUserSessions(RealmModel realm, UserModel userModel) {
|
|
||||||
MongoUserEntity user = getUserById(userModel.getId(), realm).getUser();
|
|
||||||
|
|
||||||
if (user.getOfflineUserSessions()==null) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
} else {
|
|
||||||
List<OfflineUserSessionModel> 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<OfflineClientSessionModel> getOfflineClientSessions(RealmModel realm, UserModel userModel) {
|
|
||||||
MongoUserEntity user = getUserById(userModel.getId(), realm).getUser();
|
|
||||||
|
|
||||||
List<OfflineClientSessionModel> 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<OfflineClientSessionModel> 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<MongoUserEntity> users = getMongoStore().loadEntities(MongoUserEntity.class, query, sort, firstResult, maxResults, invocationContext);
|
|
||||||
|
|
||||||
List<OfflineClientSessionModel> 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+276
@@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
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<PersistentClientSessionEntity>());
|
||||||
|
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<MongoOnlineUserSessionEntity> userSessions = getMongoStore().loadEntities(MongoOnlineUserSessionEntity.class, query, invocationContext);
|
||||||
|
for (MongoOnlineUserSessionEntity userSession : userSessions) {
|
||||||
|
removeClientSessionOfClient(userSession, client.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
List<MongoOfflineUserSessionEntity> 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<UserSessionModel> 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<UserSessionModel> 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<ClientSessionModel> 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() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
+42
@@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,8 +8,6 @@ import com.mongodb.QueryBuilder;
|
|||||||
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
|
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.OTPPolicy;
|
import org.keycloak.models.OTPPolicy;
|
||||||
import org.keycloak.models.OfflineClientSessionModel;
|
|
||||||
import org.keycloak.models.OfflineUserSessionModel;
|
|
||||||
import org.keycloak.models.ProtocolMapperModel;
|
import org.keycloak.models.ProtocolMapperModel;
|
||||||
import org.keycloak.models.UserConsentModel;
|
import org.keycloak.models.UserConsentModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
@@ -22,19 +20,15 @@ import org.keycloak.models.UserCredentialModel;
|
|||||||
import org.keycloak.models.UserCredentialValueModel;
|
import org.keycloak.models.UserCredentialValueModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.entities.CredentialEntity;
|
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.entities.UserConsentEntity;
|
||||||
import org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity;
|
import org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity;
|
||||||
import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
|
import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
|
||||||
import org.keycloak.models.mongo.utils.MongoModelUtils;
|
import org.keycloak.models.mongo.utils.MongoModelUtils;
|
||||||
import org.keycloak.models.utils.HmacOTP;
|
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
|
import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
|
||||||
import org.keycloak.util.Time;
|
import org.keycloak.util.Time;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
package org.keycloak.models.mongo.keycloak.entities;
|
||||||
|
|
||||||
|
import org.keycloak.connections.mongo.api.MongoCollection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
@MongoCollection(collectionName = "offlineUserSessions")
|
||||||
|
public class MongoOfflineUserSessionEntity extends MongoUserSessionEntity {
|
||||||
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
package org.keycloak.models.mongo.keycloak.entities;
|
||||||
|
|
||||||
|
import org.keycloak.connections.mongo.api.MongoCollection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
@MongoCollection(collectionName = "userSessions")
|
||||||
|
public class MongoOnlineUserSessionEntity extends MongoUserSessionEntity {
|
||||||
|
}
|
||||||
+15
@@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public abstract class MongoUserSessionEntity extends PersistentUserSessionEntity implements MongoIdentifiableEntity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterRemove(MongoStoreInvocationContext invocationContext) {
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
org.keycloak.models.mongo.keycloak.adapters.MongoUserSessionPersisterProviderFactory
|
||||||
@@ -30,5 +30,10 @@
|
|||||||
<groupId>org.infinispan</groupId>
|
<groupId>org.infinispan</groupId>
|
||||||
<artifactId>infinispan-core</artifactId>
|
<artifactId>infinispan-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
+13
-7
@@ -26,13 +26,16 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
|||||||
private Cache<String, SessionEntity> cache;
|
private Cache<String, SessionEntity> cache;
|
||||||
private RealmModel realm;
|
private RealmModel realm;
|
||||||
private ClientSessionEntity entity;
|
private ClientSessionEntity entity;
|
||||||
|
private boolean offline;
|
||||||
|
|
||||||
public ClientSessionAdapter(KeycloakSession session, InfinispanUserSessionProvider provider, Cache<String, SessionEntity> cache, RealmModel realm, ClientSessionEntity entity) {
|
public ClientSessionAdapter(KeycloakSession session, InfinispanUserSessionProvider provider, Cache<String, SessionEntity> cache, RealmModel realm,
|
||||||
|
ClientSessionEntity entity, boolean offline) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
this.cache = cache;
|
this.cache = cache;
|
||||||
this.realm = realm;
|
this.realm = realm;
|
||||||
this.entity = entity;
|
this.entity = entity;
|
||||||
|
this.offline = offline;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -51,8 +54,8 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserSessionModel getUserSession() {
|
public UserSessionAdapter getUserSession() {
|
||||||
return entity.getUserSession() != null ? provider.getUserSession(realm, entity.getUserSession()) : null;
|
return entity.getUserSession() != null ? provider.getUserSession(realm, entity.getUserSession(), offline) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -63,14 +66,15 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
|||||||
}
|
}
|
||||||
entity.setUserSession(null);
|
entity.setUserSession(null);
|
||||||
} else {
|
} else {
|
||||||
|
UserSessionAdapter userSessionAdapter = (UserSessionAdapter) userSession;
|
||||||
if (entity.getUserSession() != null) {
|
if (entity.getUserSession() != null) {
|
||||||
if (entity.getUserSession().equals(userSession.getId())) {
|
if (entity.getUserSession().equals(userSession.getId())) {
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
provider.dettachSession(userSession, this);
|
provider.dettachSession(userSessionAdapter, this);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
provider.attachSession(userSession, this);
|
provider.attachSession(userSessionAdapter, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
entity.setUserSession(userSession.getId());
|
entity.setUserSession(userSession.getId());
|
||||||
@@ -113,7 +117,8 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<String> getRoles() {
|
public Set<String> getRoles() {
|
||||||
return entity.getRoles();
|
if (entity.getRoles() == null || entity.getRoles().isEmpty()) return Collections.emptySet();
|
||||||
|
return new HashSet<>(entity.getRoles());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -124,7 +129,8 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<String> getProtocolMappers() {
|
public Set<String> getProtocolMappers() {
|
||||||
return entity.getProtocolMappers();
|
if (entity.getProtocolMappers() == null || entity.getProtocolMappers().isEmpty()) return Collections.emptySet();
|
||||||
|
return new HashSet<>(entity.getProtocolMappers());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+267
-50
@@ -7,6 +7,7 @@ import org.keycloak.models.ClientModel;
|
|||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakTransaction;
|
import org.keycloak.models.KeycloakTransaction;
|
||||||
|
import org.keycloak.models.ModelException;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
@@ -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.SessionEntity;
|
||||||
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
||||||
import org.keycloak.models.sessions.infinispan.mapreduce.ClientSessionMapper;
|
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.FirstResultReducer;
|
||||||
import org.keycloak.models.sessions.infinispan.mapreduce.LargestResultReducer;
|
import org.keycloak.models.sessions.infinispan.mapreduce.LargestResultReducer;
|
||||||
import org.keycloak.models.sessions.infinispan.mapreduce.SessionMapper;
|
import org.keycloak.models.sessions.infinispan.mapreduce.SessionMapper;
|
||||||
@@ -36,6 +38,7 @@ import java.util.HashSet;
|
|||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
@@ -46,18 +49,25 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||||||
|
|
||||||
private final KeycloakSession session;
|
private final KeycloakSession session;
|
||||||
private final Cache<String, SessionEntity> sessionCache;
|
private final Cache<String, SessionEntity> sessionCache;
|
||||||
|
private final Cache<String, SessionEntity> offlineSessionCache;
|
||||||
private final Cache<LoginFailureKey, LoginFailureEntity> loginFailureCache;
|
private final Cache<LoginFailureKey, LoginFailureEntity> loginFailureCache;
|
||||||
private final InfinispanKeycloakTransaction tx;
|
private final InfinispanKeycloakTransaction tx;
|
||||||
|
|
||||||
public InfinispanUserSessionProvider(KeycloakSession session, Cache<String, SessionEntity> sessionCache, Cache<LoginFailureKey, LoginFailureEntity> loginFailureCache) {
|
public InfinispanUserSessionProvider(KeycloakSession session, Cache<String, SessionEntity> sessionCache, Cache<String, SessionEntity> offlineSessionCache,
|
||||||
|
Cache<LoginFailureKey, LoginFailureEntity> loginFailureCache) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.sessionCache = sessionCache;
|
this.sessionCache = sessionCache;
|
||||||
|
this.offlineSessionCache = offlineSessionCache;
|
||||||
this.loginFailureCache = loginFailureCache;
|
this.loginFailureCache = loginFailureCache;
|
||||||
this.tx = new InfinispanKeycloakTransaction();
|
this.tx = new InfinispanKeycloakTransaction();
|
||||||
|
|
||||||
session.getTransaction().enlistAfterCompletion(tx);
|
session.getTransaction().enlistAfterCompletion(tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected Cache<String, SessionEntity> getCache(boolean offline) {
|
||||||
|
return offline ? offlineSessionCache : sessionCache;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ClientSessionModel createClientSession(RealmModel realm, ClientModel client) {
|
public ClientSessionModel createClientSession(RealmModel realm, ClientModel client) {
|
||||||
String id = KeycloakModelUtils.generateId();
|
String id = KeycloakModelUtils.generateId();
|
||||||
@@ -70,7 +80,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||||||
|
|
||||||
tx.put(sessionCache, id, entity);
|
tx.put(sessionCache, id, entity);
|
||||||
|
|
||||||
return wrap(realm, entity);
|
return wrap(realm, entity, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -95,29 +105,57 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||||||
|
|
||||||
tx.put(sessionCache, id, entity);
|
tx.put(sessionCache, id, entity);
|
||||||
|
|
||||||
return wrap(realm, entity);
|
return wrap(realm, entity, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ClientSessionModel getClientSession(RealmModel realm, String id) {
|
public ClientSessionModel getClientSession(RealmModel realm, String id) {
|
||||||
ClientSessionEntity entity = (ClientSessionEntity) sessionCache.get(id);
|
return getClientSession(realm, id, false);
|
||||||
return wrap(realm, entity);
|
}
|
||||||
|
|
||||||
|
protected ClientSessionModel getClientSession(RealmModel realm, String id, boolean offline) {
|
||||||
|
Cache<String, SessionEntity> 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
|
@Override
|
||||||
public ClientSessionModel getClientSession(String id) {
|
public ClientSessionModel getClientSession(String id) {
|
||||||
ClientSessionEntity entity = (ClientSessionEntity) sessionCache.get(id);
|
ClientSessionEntity entity = (ClientSessionEntity) sessionCache.get(id);
|
||||||
|
|
||||||
|
// Chance created in this transaction
|
||||||
|
if (entity == null) {
|
||||||
|
entity = (ClientSessionEntity) tx.get(sessionCache, id);
|
||||||
|
}
|
||||||
|
|
||||||
if (entity != null) {
|
if (entity != null) {
|
||||||
RealmModel realm = session.realms().getRealm(entity.getRealm());
|
RealmModel realm = session.realms().getRealm(entity.getRealm());
|
||||||
return wrap(realm, entity);
|
return wrap(realm, entity, false);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserSessionModel getUserSession(RealmModel realm, String id) {
|
public UserSessionModel getUserSession(RealmModel realm, String id) {
|
||||||
UserSessionEntity entity = (UserSessionEntity) sessionCache.get(id);
|
return getUserSession(realm, id, false);
|
||||||
return wrap(realm, entity);
|
}
|
||||||
|
|
||||||
|
protected UserSessionAdapter getUserSession(RealmModel realm, String id, boolean offline) {
|
||||||
|
Cache<String, SessionEntity> 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
|
@Override
|
||||||
@@ -127,7 +165,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||||||
.reducedWith(new FirstResultReducer())
|
.reducedWith(new FirstResultReducer())
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
return wrapUserSessions(realm, sessions.values());
|
return wrapUserSessions(realm, sessions.values(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -137,7 +175,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||||||
.reducedWith(new FirstResultReducer())
|
.reducedWith(new FirstResultReducer())
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
return wrapUserSessions(realm, sessions.values());
|
return wrapUserSessions(realm, sessions.values(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -147,7 +185,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||||||
.reducedWith(new FirstResultReducer())
|
.reducedWith(new FirstResultReducer())
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
List<UserSessionModel> userSessionModels = wrapUserSessions(realm, sessions.values());
|
List<UserSessionModel> userSessionModels = wrapUserSessions(realm, sessions.values(), false);
|
||||||
if (userSessionModels.isEmpty()) return null;
|
if (userSessionModels.isEmpty()) return null;
|
||||||
return userSessionModels.get(0);
|
return userSessionModels.get(0);
|
||||||
}
|
}
|
||||||
@@ -159,7 +197,13 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
|
public List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
|
||||||
Map<String, Integer> map = new MapReduceTask(sessionCache)
|
return getUserSessions(realm, client, firstResult, maxResults, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults, boolean offline) {
|
||||||
|
Cache<String, SessionEntity> cache = getCache(offline);
|
||||||
|
|
||||||
|
Map<String, Integer> map = new MapReduceTask(cache)
|
||||||
.mappedWith(ClientSessionMapper.create(realm.getId()).client(client.getId()).emitUserSessionAndTimestamp())
|
.mappedWith(ClientSessionMapper.create(realm.getId()).client(client.getId()).emitUserSessionAndTimestamp())
|
||||||
.reducedWith(new LargestResultReducer())
|
.reducedWith(new LargestResultReducer())
|
||||||
.execute();
|
.execute();
|
||||||
@@ -192,9 +236,9 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||||||
|
|
||||||
List<UserSessionModel> userSessions = new LinkedList<UserSessionModel>();
|
List<UserSessionModel> userSessions = new LinkedList<UserSessionModel>();
|
||||||
for (Map.Entry<String, Integer> e : sessionTimestamps) {
|
for (Map.Entry<String, Integer> e : sessionTimestamps) {
|
||||||
UserSessionEntity userSessionEntity = (UserSessionEntity) sessionCache.get(e.getKey());
|
UserSessionEntity userSessionEntity = (UserSessionEntity) cache.get(e.getKey());
|
||||||
if (userSessionEntity != null) {
|
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())
|
.reducedWith(new FirstResultReducer())
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
return wrapUserSessions(realm, sessions.values());
|
return wrapUserSessions(realm, sessions.values(), false);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getActiveUserSessions(RealmModel realm, ClientModel client) {
|
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<String, SessionEntity> cache = getCache(offline);
|
||||||
|
|
||||||
|
Map map = new MapReduceTask(cache)
|
||||||
.mappedWith(ClientSessionMapper.create(realm.getId()).client(client.getId()).emitUserSessionAndTimestamp())
|
.mappedWith(ClientSessionMapper.create(realm.getId()).client(client.getId()).emitUserSessionAndTimestamp())
|
||||||
.reducedWith(new LargestResultReducer()).execute();
|
.reducedWith(new LargestResultReducer()).execute();
|
||||||
|
|
||||||
@@ -234,13 +284,19 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeUserSessions(RealmModel realm, UserModel user) {
|
public void removeUserSessions(RealmModel realm, UserModel user) {
|
||||||
Map<String, String> sessions = new MapReduceTask(sessionCache)
|
removeUserSessions(realm, user, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void removeUserSessions(RealmModel realm, UserModel user, boolean offline) {
|
||||||
|
Cache<String, SessionEntity> cache = getCache(offline);
|
||||||
|
|
||||||
|
Map<String, String> sessions = new MapReduceTask(cache)
|
||||||
.mappedWith(UserSessionMapper.create(realm.getId()).user(user.getId()).emitKey())
|
.mappedWith(UserSessionMapper.create(realm.getId()).user(user.getId()).emitKey())
|
||||||
.reducedWith(new FirstResultReducer())
|
.reducedWith(new FirstResultReducer())
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
for (String id : sessions.keySet()) {
|
for (String id : sessions.keySet()) {
|
||||||
removeUserSession(realm, id);
|
removeUserSession(realm, id, offline);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,13 +327,19 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeUserSessions(RealmModel realm) {
|
public void removeUserSessions(RealmModel realm) {
|
||||||
Map<String, String> ids = new MapReduceTask(sessionCache)
|
removeUserSessions(realm, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void removeUserSessions(RealmModel realm, boolean offline) {
|
||||||
|
Cache<String, SessionEntity> cache = getCache(offline);
|
||||||
|
|
||||||
|
Map<String, String> ids = new MapReduceTask(cache)
|
||||||
.mappedWith(SessionMapper.create(realm.getId()).emitKey())
|
.mappedWith(SessionMapper.create(realm.getId()).emitKey())
|
||||||
.reducedWith(new FirstResultReducer())
|
.reducedWith(new FirstResultReducer())
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
for (String id : ids.keySet()) {
|
for (String id : ids.keySet()) {
|
||||||
sessionCache.remove(id);
|
cache.remove(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,25 +381,39 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRealmRemoved(RealmModel realm) {
|
public void onRealmRemoved(RealmModel realm) {
|
||||||
removeUserSessions(realm);
|
removeUserSessions(realm, true);
|
||||||
|
removeUserSessions(realm, false);
|
||||||
removeAllUserLoginFailures(realm);
|
removeAllUserLoginFailures(realm);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClientRemoved(RealmModel realm, ClientModel client) {
|
public void onClientRemoved(RealmModel realm, ClientModel client) {
|
||||||
Map<String, String> map = new MapReduceTask(sessionCache)
|
onClientRemoved(realm, client, true);
|
||||||
.mappedWith(ClientSessionMapper.create(realm.getId()).client(client.getId()).emitKey())
|
onClientRemoved(realm, client, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onClientRemoved(RealmModel realm, ClientModel client, boolean offline) {
|
||||||
|
Cache<String, SessionEntity> cache = getCache(offline);
|
||||||
|
|
||||||
|
Map<String, ClientSessionEntity> map = new MapReduceTask(cache)
|
||||||
|
.mappedWith(ClientSessionMapper.create(realm.getId()).client(client.getId()))
|
||||||
.reducedWith(new FirstResultReducer())
|
.reducedWith(new FirstResultReducer())
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
for (String id : map.keySet()) {
|
for (Map.Entry<String, ClientSessionEntity> entry : map.entrySet()) {
|
||||||
tx.remove(sessionCache, id);
|
|
||||||
|
// detach from userSession
|
||||||
|
ClientSessionAdapter adapter = wrap(realm, entry.getValue(), offline);
|
||||||
|
adapter.setUserSession(null);
|
||||||
|
|
||||||
|
tx.remove(cache, entry.getKey());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUserRemoved(RealmModel realm, UserModel user) {
|
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.getUsername()));
|
||||||
loginFailureCache.remove(new LoginFailureKey(realm.getId(), user.getEmail()));
|
loginFailureCache.remove(new LoginFailureKey(realm.getId(), user.getEmail()));
|
||||||
@@ -347,20 +423,26 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||||||
public void close() {
|
public void close() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void attachSession(UserSessionModel userSession, ClientSessionModel clientSession) {
|
void attachSession(UserSessionAdapter userSession, ClientSessionModel clientSession) {
|
||||||
UserSessionEntity entity = ((UserSessionAdapter) userSession).getEntity();
|
UserSessionEntity entity = userSession.getEntity();
|
||||||
String clientSessionId = clientSession.getId();
|
String clientSessionId = clientSession.getId();
|
||||||
if (entity.getClientSessions() == null) {
|
if (entity.getClientSessions() == null) {
|
||||||
entity.setClientSessions(new HashSet<String>());
|
entity.setClientSessions(new HashSet<String>());
|
||||||
}
|
}
|
||||||
if (!entity.getClientSessions().contains(clientSessionId)) {
|
if (!entity.getClientSessions().contains(clientSessionId)) {
|
||||||
entity.getClientSessions().add(clientSessionId);
|
entity.getClientSessions().add(clientSessionId);
|
||||||
tx.replace(sessionCache, entity.getId(), entity);
|
userSession.update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeClientSession(RealmModel realm, ClientSessionModel clientSession) {
|
public void removeClientSession(RealmModel realm, ClientSessionModel clientSession) {
|
||||||
|
removeClientSession(realm, clientSession, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void removeClientSession(RealmModel realm, ClientSessionModel clientSession, boolean offline) {
|
||||||
|
Cache<String, SessionEntity> cache = getCache(offline);
|
||||||
|
|
||||||
UserSessionModel userSession = clientSession.getUserSession();
|
UserSessionModel userSession = clientSession.getUserSession();
|
||||||
if (userSession != null) {
|
if (userSession != null) {
|
||||||
UserSessionEntity entity = ((UserSessionAdapter) userSession).getEntity();
|
UserSessionEntity entity = ((UserSessionAdapter) userSession).getEntity();
|
||||||
@@ -368,34 +450,40 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||||||
entity.getClientSessions().remove(clientSession.getId());
|
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) {
|
void dettachSession(UserSessionAdapter userSession, ClientSessionModel clientSession) {
|
||||||
UserSessionEntity entity = ((UserSessionAdapter) userSession).getEntity();
|
UserSessionEntity entity = userSession.getEntity();
|
||||||
String clientSessionId = clientSession.getId();
|
String clientSessionId = clientSession.getId();
|
||||||
if (entity.getClientSessions() != null && entity.getClientSessions().contains(clientSessionId)) {
|
if (entity.getClientSessions() != null && entity.getClientSessions().contains(clientSessionId)) {
|
||||||
entity.getClientSessions().remove(clientSessionId);
|
entity.getClientSessions().remove(clientSessionId);
|
||||||
if (entity.getClientSessions().isEmpty()) {
|
if (entity.getClientSessions().isEmpty()) {
|
||||||
entity.setClientSessions(null);
|
entity.setClientSessions(null);
|
||||||
}
|
}
|
||||||
tx.replace(sessionCache, entity.getId(), entity);
|
userSession.update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void removeUserSession(RealmModel realm, String userSessionId) {
|
protected void removeUserSession(RealmModel realm, String userSessionId) {
|
||||||
tx.remove(sessionCache, userSessionId);
|
removeUserSession(realm, userSessionId, false);
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, String> map = new MapReduceTask(sessionCache)
|
protected void removeUserSession(RealmModel realm, String userSessionId, boolean offline) {
|
||||||
|
Cache<String, SessionEntity> cache = getCache(offline);
|
||||||
|
|
||||||
|
tx.remove(cache, userSessionId);
|
||||||
|
|
||||||
|
Map<String, String> map = new MapReduceTask(cache)
|
||||||
.mappedWith(ClientSessionMapper.create(realm.getId()).userSession(userSessionId).emitKey())
|
.mappedWith(ClientSessionMapper.create(realm.getId()).userSession(userSessionId).emitKey())
|
||||||
.reducedWith(new FirstResultReducer())
|
.reducedWith(new FirstResultReducer())
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
for (String id : map.keySet()) {
|
for (String id : map.keySet()) {
|
||||||
tx.remove(sessionCache, id);
|
tx.remove(cache, id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -404,20 +492,22 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||||||
return tx;
|
return tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
UserSessionModel wrap(RealmModel realm, UserSessionEntity entity) {
|
UserSessionAdapter wrap(RealmModel realm, UserSessionEntity entity, boolean offline) {
|
||||||
return entity != null ? new UserSessionAdapter(session, this, sessionCache, realm, entity) : null;
|
Cache<String, SessionEntity> cache = getCache(offline);
|
||||||
|
return entity != null ? new UserSessionAdapter(session, this, cache, realm, entity, offline) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<UserSessionModel> wrapUserSessions(RealmModel realm, Collection<UserSessionEntity> entities) {
|
List<UserSessionModel> wrapUserSessions(RealmModel realm, Collection<UserSessionEntity> entities, boolean offline) {
|
||||||
List<UserSessionModel> models = new LinkedList<UserSessionModel>();
|
List<UserSessionModel> models = new LinkedList<UserSessionModel>();
|
||||||
for (UserSessionEntity e : entities) {
|
for (UserSessionEntity e : entities) {
|
||||||
models.add(wrap(realm, e));
|
models.add(wrap(realm, e, offline));
|
||||||
}
|
}
|
||||||
return models;
|
return models;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientSessionModel wrap(RealmModel realm, ClientSessionEntity entity) {
|
ClientSessionAdapter wrap(RealmModel realm, ClientSessionEntity entity, boolean offline) {
|
||||||
return entity != null ? new ClientSessionAdapter(session, this, sessionCache, realm, entity) : null;
|
Cache<String, SessionEntity> 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;
|
return entity != null ? new UsernameLoginFailureAdapter(this, loginFailureCache, key, entity) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ClientSessionModel> wrapClientSessions(RealmModel realm, Collection<ClientSessionEntity> entities) {
|
List<ClientSessionModel> wrapClientSessions(RealmModel realm, Collection<ClientSessionEntity> entities, boolean offline) {
|
||||||
List<ClientSessionModel> models = new LinkedList<ClientSessionModel>();
|
List<ClientSessionModel> models = new LinkedList<ClientSessionModel>();
|
||||||
for (ClientSessionEntity e : entities) {
|
for (ClientSessionEntity e : entities) {
|
||||||
models.add(wrap(realm, e));
|
models.add(wrap(realm, e, offline));
|
||||||
}
|
}
|
||||||
return models;
|
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<ClientSessionModel> getOfflineClientSessions(RealmModel realm, UserModel user) {
|
||||||
|
Map<String, UserSessionEntity> sessions = new MapReduceTask(offlineSessionCache)
|
||||||
|
.mappedWith(UserSessionMapper.create(realm.getId()).user(user.getId()))
|
||||||
|
.reducedWith(new FirstResultReducer())
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
List<ClientSessionEntity> clientSessions = new LinkedList<>();
|
||||||
|
for (UserSessionEntity userSession : sessions.values()) {
|
||||||
|
Set<String> 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<UserSessionModel> getOfflineUserSessions(RealmModel realm, ClientModel client, int first, int max) {
|
||||||
|
return getUserSessions(realm, client, first, max, true);
|
||||||
|
}
|
||||||
|
|
||||||
class InfinispanKeycloakTransaction implements KeycloakTransaction {
|
class InfinispanKeycloakTransaction implements KeycloakTransaction {
|
||||||
|
|
||||||
private boolean active;
|
private boolean active;
|
||||||
@@ -478,17 +667,19 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||||||
public void put(Cache cache, Object key, Object value) {
|
public void put(Cache cache, Object key, Object value) {
|
||||||
log.tracev("Adding cache operation: {0} on {1}", CacheOperation.ADD, key);
|
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");
|
throw new IllegalStateException("Can't add session: task in progress for session");
|
||||||
} else {
|
} 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) {
|
public void replace(Cache cache, Object key, Object value) {
|
||||||
log.tracev("Adding cache operation: {0} on {1}", CacheOperation.REPLACE, key);
|
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) {
|
if (current != null) {
|
||||||
switch (current.operation) {
|
switch (current.operation) {
|
||||||
case ADD:
|
case ADD:
|
||||||
@@ -499,14 +690,40 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} 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) {
|
public void remove(Cache cache, Object key) {
|
||||||
log.tracev("Adding cache operation: {0} on {1}", CacheOperation.REMOVE, 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 {
|
public class CacheTask {
|
||||||
|
|||||||
+74
-14
@@ -7,12 +7,18 @@ import org.keycloak.Config;
|
|||||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.KeycloakSessionTask;
|
||||||
import org.keycloak.models.UserSessionProvider;
|
import org.keycloak.models.UserSessionProvider;
|
||||||
import org.keycloak.models.UserSessionProviderFactory;
|
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.MemUserSessionProviderFactory;
|
||||||
|
import org.keycloak.models.sessions.infinispan.compat.SimpleUserSessionInitializer;
|
||||||
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
|
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
|
||||||
import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
|
import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
|
||||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
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
|
* 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 static final Logger log = Logger.getLogger(InfinispanUserSessionProviderFactory.class);
|
||||||
|
|
||||||
|
private Config.Scope config;
|
||||||
private Boolean compatMode;
|
private Boolean compatMode;
|
||||||
private MemUserSessionProviderFactory compatProviderFactory;
|
private MemUserSessionProviderFactory compatProviderFactory;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserSessionProvider create(KeycloakSession session) {
|
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) {
|
if (!compatMode) {
|
||||||
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
|
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
|
||||||
Cache<String, SessionEntity> cache = connections.getCache(InfinispanConnectionProvider.SESSION_CACHE_NAME);
|
Cache<String, SessionEntity> cache = connections.getCache(InfinispanConnectionProvider.SESSION_CACHE_NAME);
|
||||||
|
Cache<String, SessionEntity> offlineSessionsCache = connections.getCache(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME);
|
||||||
Cache<LoginFailureKey, LoginFailureEntity> loginFailures = connections.getCache(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME);
|
Cache<LoginFailureKey, LoginFailureEntity> loginFailures = connections.getCache(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME);
|
||||||
return new InfinispanUserSessionProvider(session, cache, loginFailures);
|
return new InfinispanUserSessionProvider(session, cache, offlineSessionsCache, loginFailures);
|
||||||
} else {
|
} else {
|
||||||
return compatProviderFactory.create(session);
|
return compatProviderFactory.create(session);
|
||||||
}
|
}
|
||||||
@@ -53,11 +50,67 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(Config.Scope config) {
|
public void init(Config.Scope config) {
|
||||||
|
this.config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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<String, SessionEntity> 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
|
@Override
|
||||||
@@ -72,11 +125,18 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
|||||||
return "infinispan";
|
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")) {
|
if (Version.getVersionShort() < Version.getVersionShort("5.3.0.Final")) {
|
||||||
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
|
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
|
||||||
Cache<String, SessionEntity> cache = connections.getCache(InfinispanConnectionProvider.SESSION_CACHE_NAME);
|
Cache<String, SessionEntity> cache = connections.getCache(InfinispanConnectionProvider.SESSION_CACHE_NAME);
|
||||||
if (cache.getAdvancedCache().getRpcManager() == null) {
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-4
@@ -31,18 +31,27 @@ public class UserSessionAdapter implements UserSessionModel {
|
|||||||
|
|
||||||
private final UserSessionEntity entity;
|
private final UserSessionEntity entity;
|
||||||
|
|
||||||
public UserSessionAdapter(KeycloakSession session, InfinispanUserSessionProvider provider, Cache<String, SessionEntity> cache, RealmModel realm, UserSessionEntity entity) {
|
private final boolean offline;
|
||||||
|
|
||||||
|
public UserSessionAdapter(KeycloakSession session, InfinispanUserSessionProvider provider, Cache<String, SessionEntity> cache, RealmModel realm,
|
||||||
|
UserSessionEntity entity, boolean offline) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
this.cache = cache;
|
this.cache = cache;
|
||||||
this.realm = realm;
|
this.realm = realm;
|
||||||
this.entity = entity;
|
this.entity = entity;
|
||||||
|
this.offline = offline;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return entity.getId();
|
return entity.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RealmModel getRealm() {
|
||||||
|
return realm;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getBrokerSessionId() {
|
public String getBrokerSessionId() {
|
||||||
return entity.getBrokerSessionId();
|
return entity.getBrokerSessionId();
|
||||||
@@ -129,14 +138,14 @@ public class UserSessionAdapter implements UserSessionModel {
|
|||||||
@Override
|
@Override
|
||||||
public List<ClientSessionModel> getClientSessions() {
|
public List<ClientSessionModel> getClientSessions() {
|
||||||
if (entity.getClientSessions() != null) {
|
if (entity.getClientSessions() != null) {
|
||||||
List<ClientSessionEntity> clientSessions = new LinkedList<ClientSessionEntity>();
|
List<ClientSessionModel> clientSessions = new LinkedList<>();
|
||||||
for (String c : entity.getClientSessions()) {
|
for (String c : entity.getClientSessions()) {
|
||||||
ClientSessionEntity clientSession = (ClientSessionEntity) cache.get(c);
|
ClientSessionModel clientSession = provider.getClientSession(realm, c, offline);
|
||||||
if (clientSession != null) {
|
if (clientSession != null) {
|
||||||
clientSessions.add(clientSession);
|
clientSessions.add(clientSession);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return provider.wrapClientSessions(realm, clientSessions);
|
return clientSessions;
|
||||||
} else {
|
} else {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|||||||
+189
-30
@@ -4,6 +4,7 @@ import org.keycloak.models.ClientModel;
|
|||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.ModelDuplicateException;
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
|
import org.keycloak.models.ModelException;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
@@ -38,13 +39,21 @@ public class MemUserSessionProvider implements UserSessionProvider {
|
|||||||
private final ConcurrentHashMap<String, ClientSessionEntity> clientSessions;
|
private final ConcurrentHashMap<String, ClientSessionEntity> clientSessions;
|
||||||
private final ConcurrentHashMap<UsernameLoginFailureKey, UsernameLoginFailureEntity> loginFailures;
|
private final ConcurrentHashMap<UsernameLoginFailureKey, UsernameLoginFailureEntity> loginFailures;
|
||||||
|
|
||||||
public MemUserSessionProvider(KeycloakSession session, ConcurrentHashMap<String, UserSessionEntity> userSessions, ConcurrentHashMap<String, String> userSessionsByBrokerSessionId, ConcurrentHashMap<String, Set<String>> userSessionsByBrokerUserId, ConcurrentHashMap<String, ClientSessionEntity> clientSessions, ConcurrentHashMap<UsernameLoginFailureKey, UsernameLoginFailureEntity> loginFailures) {
|
private final ConcurrentHashMap<String, UserSessionEntity> offlineUserSessions;
|
||||||
|
private final ConcurrentHashMap<String, ClientSessionEntity> offlineClientSessions;
|
||||||
|
|
||||||
|
public MemUserSessionProvider(KeycloakSession session, ConcurrentHashMap<String, UserSessionEntity> userSessions, ConcurrentHashMap<String, String> userSessionsByBrokerSessionId,
|
||||||
|
ConcurrentHashMap<String, Set<String>> userSessionsByBrokerUserId, ConcurrentHashMap<String, ClientSessionEntity> clientSessions,
|
||||||
|
ConcurrentHashMap<UsernameLoginFailureKey, UsernameLoginFailureEntity> loginFailures,
|
||||||
|
ConcurrentHashMap<String, UserSessionEntity> offlineUserSessions, ConcurrentHashMap<String, ClientSessionEntity> offlineClientSessions) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.userSessions = userSessions;
|
this.userSessions = userSessions;
|
||||||
this.clientSessions = clientSessions;
|
this.clientSessions = clientSessions;
|
||||||
this.loginFailures = loginFailures;
|
this.loginFailures = loginFailures;
|
||||||
this.userSessionsByBrokerSessionId = userSessionsByBrokerSessionId;
|
this.userSessionsByBrokerSessionId = userSessionsByBrokerSessionId;
|
||||||
this.userSessionsByBrokerUserId = userSessionsByBrokerUserId;
|
this.userSessionsByBrokerUserId = userSessionsByBrokerUserId;
|
||||||
|
this.offlineUserSessions = offlineUserSessions;
|
||||||
|
this.offlineClientSessions = offlineClientSessions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -189,6 +198,12 @@ public class MemUserSessionProvider implements UserSessionProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client) {
|
public List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client) {
|
||||||
|
return getUserSessions(realm, client, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client, boolean offline) {
|
||||||
|
ConcurrentHashMap<String, ClientSessionEntity> clientSessions = offline ? this.offlineClientSessions : this.clientSessions;
|
||||||
|
|
||||||
List<UserSessionEntity> userSessionEntities = new LinkedList<UserSessionEntity>();
|
List<UserSessionEntity> userSessionEntities = new LinkedList<UserSessionEntity>();
|
||||||
for (ClientSessionEntity s : clientSessions.values()) {
|
for (ClientSessionEntity s : clientSessions.values()) {
|
||||||
String realmId = realm.getId();
|
String realmId = realm.getId();
|
||||||
@@ -210,7 +225,11 @@ public class MemUserSessionProvider implements UserSessionProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
|
public List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
|
||||||
List<UserSessionModel> userSessions = getUserSessions(realm, client);
|
return getUserSessions(realm, client, firstResult, maxResults, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults, boolean offline) {
|
||||||
|
List<UserSessionModel> userSessions = getUserSessions(realm, client, offline);
|
||||||
if (firstResult > userSessions.size()) {
|
if (firstResult > userSessions.size()) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
@@ -221,7 +240,7 @@ public class MemUserSessionProvider implements UserSessionProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getActiveUserSessions(RealmModel realm, ClientModel client) {
|
public int getActiveUserSessions(RealmModel realm, ClientModel client) {
|
||||||
return getUserSessions(realm, client).size();
|
return getUserSessions(realm, client, false).size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -229,40 +248,51 @@ public class MemUserSessionProvider implements UserSessionProvider {
|
|||||||
UserSessionEntity entity = getUserSessionEntity(realm, session.getId());
|
UserSessionEntity entity = getUserSessionEntity(realm, session.getId());
|
||||||
if (entity != null) {
|
if (entity != null) {
|
||||||
userSessions.remove(entity.getId());
|
userSessions.remove(entity.getId());
|
||||||
remove(entity);
|
remove(entity, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeUserSessions(RealmModel realm, UserModel user) {
|
public void removeUserSessions(RealmModel realm, UserModel user) {
|
||||||
Iterator<UserSessionEntity> itr = userSessions.values().iterator();
|
removeUserSessions(realm, user, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void removeUserSessions(RealmModel realm, UserModel user, boolean offline) {
|
||||||
|
Iterator<UserSessionEntity> itr = offline ? offlineUserSessions.values().iterator() : userSessions.values().iterator();
|
||||||
|
|
||||||
while (itr.hasNext()) {
|
while (itr.hasNext()) {
|
||||||
UserSessionEntity s = itr.next();
|
UserSessionEntity s = itr.next();
|
||||||
if (s.getRealm().equals(realm.getId()) && s.getUser().equals(user.getId())) {
|
if (s.getRealm().equals(realm.getId()) && s.getUser().equals(user.getId())) {
|
||||||
itr.remove();
|
itr.remove();
|
||||||
remove(s);
|
remove(s, offline);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void remove(UserSessionEntity s) {
|
protected void remove(UserSessionEntity s, boolean offline) {
|
||||||
if (s.getBrokerSessionId() != null) {
|
if (offline) {
|
||||||
userSessionsByBrokerSessionId.remove(s.getBrokerSessionId());
|
for (ClientSessionEntity clientSession : s.getClientSessions()) {
|
||||||
}
|
offlineClientSessions.remove(clientSession.getId());
|
||||||
if (s.getBrokerUserId() != null) {
|
}
|
||||||
Set<String> set = userSessionsByBrokerUserId.get(s.getBrokerUserId());
|
} else {
|
||||||
if (set != null) {
|
if (s.getBrokerSessionId() != null) {
|
||||||
synchronized (set) {
|
userSessionsByBrokerSessionId.remove(s.getBrokerSessionId());
|
||||||
set.remove(s.getId());
|
}
|
||||||
// this is a race condition :(
|
if (s.getBrokerUserId() != null) {
|
||||||
// Since it will be very rare for a user to have concurrent sessions, I'm hoping we never hit this
|
Set<String> set = userSessionsByBrokerUserId.get(s.getBrokerUserId());
|
||||||
if (set.isEmpty()) userSessionsByBrokerUserId.remove(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
|
@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())) {
|
if (s.getRealm().equals(realm.getId()) && (s.getLastSessionRefresh() < Time.currentTime() - realm.getSsoSessionIdleTimeout() || s.getStarted() < Time.currentTime() - realm.getSsoSessionMaxLifespan())) {
|
||||||
itr.remove();
|
itr.remove();
|
||||||
|
|
||||||
remove(s);
|
remove(s, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int expired = Time.currentTime() - RealmInfoUtil.getDettachedClientSessionLifespan(realm);
|
int expired = Time.currentTime() - RealmInfoUtil.getDettachedClientSessionLifespan(realm);
|
||||||
@@ -288,16 +318,19 @@ public class MemUserSessionProvider implements UserSessionProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeUserSessions(RealmModel realm) {
|
public void removeUserSessions(RealmModel realm) {
|
||||||
Iterator<UserSessionEntity> itr = userSessions.values().iterator();
|
removeUserSessions(realm, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void removeUserSessions(RealmModel realm, boolean offline) {
|
||||||
|
Iterator<UserSessionEntity> itr = offline ? offlineUserSessions.values().iterator() : userSessions.values().iterator();
|
||||||
while (itr.hasNext()) {
|
while (itr.hasNext()) {
|
||||||
UserSessionEntity s = itr.next();
|
UserSessionEntity s = itr.next();
|
||||||
if (s.getRealm().equals(realm.getId())) {
|
if (s.getRealm().equals(realm.getId())) {
|
||||||
itr.remove();
|
itr.remove();
|
||||||
|
remove(s, offline);
|
||||||
remove(s);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Iterator<ClientSessionEntity> citr = clientSessions.values().iterator();
|
Iterator<ClientSessionEntity> citr = offline ? offlineClientSessions.values().iterator() : clientSessions.values().iterator();
|
||||||
while (citr.hasNext()) {
|
while (citr.hasNext()) {
|
||||||
ClientSessionEntity c = citr.next();
|
ClientSessionEntity c = citr.next();
|
||||||
if (c.getSession() == null && c.getRealmId().equals(realm.getId())) {
|
if (c.getSession() == null && c.getRealmId().equals(realm.getId())) {
|
||||||
@@ -340,15 +373,23 @@ public class MemUserSessionProvider implements UserSessionProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRealmRemoved(RealmModel realm) {
|
public void onRealmRemoved(RealmModel realm) {
|
||||||
removeUserSessions(realm);
|
removeUserSessions(realm, true);
|
||||||
|
removeUserSessions(realm, false);
|
||||||
removeAllUserLoginFailures(realm);
|
removeAllUserLoginFailures(realm);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClientRemoved(RealmModel realm, ClientModel client) {
|
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<String, ClientSessionEntity> clientSessionsMap = offline ? offlineClientSessions : clientSessions;
|
||||||
|
|
||||||
|
for (ClientSessionEntity e : clientSessionsMap.values()) {
|
||||||
if (e.getRealmId().equals(realm.getId()) && e.getClientId().equals(client.getId())) {
|
if (e.getRealmId().equals(realm.getId()) && e.getClientId().equals(client.getId())) {
|
||||||
clientSessions.remove(e.getId());
|
clientSessionsMap.remove(e.getId());
|
||||||
e.getSession().removeClientSession(e);
|
e.getSession().removeClientSession(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -356,12 +397,130 @@ public class MemUserSessionProvider implements UserSessionProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUserRemoved(RealmModel realm, UserModel user) {
|
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.getUsername()));
|
||||||
loginFailures.remove(new UsernameLoginFailureKey(realm.getId(), user.getEmail()));
|
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<ClientSessionModel> getOfflineClientSessions(RealmModel realm, UserModel user) {
|
||||||
|
List<ClientSessionModel> 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<UserSessionModel> getOfflineUserSessions(RealmModel realm, ClientModel client, int first, int max) {
|
||||||
|
return getUserSessions(realm, client, first, max, true);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-1
@@ -26,8 +26,12 @@ public class MemUserSessionProviderFactory {
|
|||||||
private final ConcurrentHashMap<String, String> userSessionsByBrokerSessionId = new ConcurrentHashMap<>();
|
private final ConcurrentHashMap<String, String> userSessionsByBrokerSessionId = new ConcurrentHashMap<>();
|
||||||
private final ConcurrentHashMap<String, Set<String>> userSessionsByBrokerUserId = new ConcurrentHashMap<>();
|
private final ConcurrentHashMap<String, Set<String>> userSessionsByBrokerUserId = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private ConcurrentHashMap<String, UserSessionEntity> offlineUserSessions = new ConcurrentHashMap<String, UserSessionEntity>();
|
||||||
|
private ConcurrentHashMap<String, ClientSessionEntity> offlineClientSessions = new ConcurrentHashMap<String, ClientSessionEntity>();
|
||||||
|
|
||||||
public UserSessionProvider create(KeycloakSession session) {
|
public UserSessionProvider create(KeycloakSession session) {
|
||||||
return new MemUserSessionProvider(session, userSessions, userSessionsByBrokerSessionId, userSessionsByBrokerUserId, clientSessions, loginFailures);
|
return new MemUserSessionProvider(session, userSessions, userSessionsByBrokerSessionId, userSessionsByBrokerUserId, clientSessions, loginFailures,
|
||||||
|
offlineUserSessions, offlineClientSessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void close() {
|
public void close() {
|
||||||
|
|||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
package org.keycloak.models.sessions.infinispan.compat;
|
||||||
|
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.KeycloakSessionTask;
|
||||||
|
import org.keycloak.models.sessions.infinispan.initializer.SessionLoader;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class SimpleUserSessionInitializer {
|
||||||
|
|
||||||
|
private final KeycloakSessionFactory sessionFactory;
|
||||||
|
private final SessionLoader sessionLoader;
|
||||||
|
private final int sessionsPerSegment;
|
||||||
|
|
||||||
|
public SimpleUserSessionInitializer(KeycloakSessionFactory sessionFactory, SessionLoader sessionLoader, int sessionsPerSegment) {
|
||||||
|
this.sessionFactory = sessionFactory;
|
||||||
|
this.sessionLoader = sessionLoader;
|
||||||
|
this.sessionsPerSegment = sessionsPerSegment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadPersistentSessions() {
|
||||||
|
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(KeycloakSession session) {
|
||||||
|
int count = sessionLoader.getSessionsCount(session);
|
||||||
|
for (int i=0 ; i<=count ; i+=sessionsPerSegment) {
|
||||||
|
sessionLoader.loadSessions(session, i, sessionsPerSegment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
@@ -39,6 +39,11 @@ public class UserSessionAdapter implements UserSessionModel {
|
|||||||
return entity.getId();
|
return entity.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RealmModel getRealm() {
|
||||||
|
return realm;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getBrokerSessionId() {
|
public String getBrokerSessionId() {
|
||||||
return entity.getBrokerSessionId();
|
return entity.getBrokerSessionId();
|
||||||
|
|||||||
-10
@@ -22,8 +22,6 @@ public class ClientSessionEntity extends SessionEntity {
|
|||||||
|
|
||||||
private String redirectUri;
|
private String redirectUri;
|
||||||
|
|
||||||
private String state;
|
|
||||||
|
|
||||||
private int timestamp;
|
private int timestamp;
|
||||||
|
|
||||||
private String action;
|
private String action;
|
||||||
@@ -69,14 +67,6 @@ public class ClientSessionEntity extends SessionEntity {
|
|||||||
this.redirectUri = redirectUri;
|
this.redirectUri = redirectUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getState() {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setState(String state) {
|
|
||||||
this.state = state;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getTimestamp() {
|
public int getTimestamp() {
|
||||||
return timestamp;
|
return timestamp;
|
||||||
}
|
}
|
||||||
|
|||||||
+268
@@ -0,0 +1,268 @@
|
|||||||
|
package org.keycloak.models.sessions.infinispan.initializer;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.infinispan.Cache;
|
||||||
|
import org.infinispan.context.Flag;
|
||||||
|
import org.infinispan.distexec.DefaultExecutorService;
|
||||||
|
import org.infinispan.distexec.DistributedExecutorService;
|
||||||
|
import org.infinispan.notifications.Listener;
|
||||||
|
import org.infinispan.notifications.cachemanagerlistener.annotation.ViewChanged;
|
||||||
|
import org.infinispan.notifications.cachemanagerlistener.event.ViewChangedEvent;
|
||||||
|
import org.infinispan.remoting.transport.Transport;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.KeycloakSessionTask;
|
||||||
|
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Startup initialization for reading persistent userSessions/clientSessions to be filled into infinispan/memory . In cluster,
|
||||||
|
* the initialization is distributed among all cluster nodes, so the startup time is even faster
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class InfinispanUserSessionInitializer {
|
||||||
|
|
||||||
|
private static final Logger log = Logger.getLogger(InfinispanUserSessionInitializer.class);
|
||||||
|
|
||||||
|
private static final String STATE_KEY_PREFIX = "initializerState";
|
||||||
|
|
||||||
|
private final KeycloakSessionFactory sessionFactory;
|
||||||
|
private final Cache<String, SessionEntity> cache;
|
||||||
|
private final SessionLoader sessionLoader;
|
||||||
|
private final int maxErrors;
|
||||||
|
private final int sessionsPerSegment;
|
||||||
|
private final String stateKey;
|
||||||
|
|
||||||
|
private volatile CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
|
||||||
|
|
||||||
|
public InfinispanUserSessionInitializer(KeycloakSessionFactory sessionFactory, Cache<String, SessionEntity> cache, SessionLoader sessionLoader, int maxErrors, int sessionsPerSegment, String stateKeySuffix) {
|
||||||
|
this.sessionFactory = sessionFactory;
|
||||||
|
this.cache = cache;
|
||||||
|
this.sessionLoader = sessionLoader;
|
||||||
|
this.maxErrors = maxErrors;
|
||||||
|
this.sessionsPerSegment = sessionsPerSegment;
|
||||||
|
this.stateKey = STATE_KEY_PREFIX + "::" + stateKeySuffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initCache() {
|
||||||
|
this.cache.getAdvancedCache().getComponentRegistry().registerComponent(sessionFactory, KeycloakSessionFactory.class);
|
||||||
|
cache.getCacheManager().addListener(new ViewChangeListener());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void loadPersistentSessions() {
|
||||||
|
if (isFinished()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!isFinished()) {
|
||||||
|
if (!isCoordinator()) {
|
||||||
|
try {
|
||||||
|
latch.await(500, TimeUnit.MILLISECONDS);
|
||||||
|
} catch (InterruptedException ie) {
|
||||||
|
log.error("Interrupted", ie);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
startLoading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private boolean isFinished() {
|
||||||
|
InitializerState stateEntity = (InitializerState) cache.get(stateKey);
|
||||||
|
return stateEntity != null && stateEntity.isFinished();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private InitializerState getOrCreateInitializerState() {
|
||||||
|
InitializerState state = (InitializerState) cache.get(stateKey);
|
||||||
|
if (state == null) {
|
||||||
|
final int[] count = new int[1];
|
||||||
|
|
||||||
|
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||||
|
@Override
|
||||||
|
public void run(KeycloakSession session) {
|
||||||
|
count[0] = sessionLoader.getSessionsCount(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
state = new InitializerState();
|
||||||
|
state.init(count[0], sessionsPerSegment);
|
||||||
|
saveStateToCache(state);
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void saveStateToCache(final InitializerState state) {
|
||||||
|
|
||||||
|
// 3 attempts to send the message (it may fail if some node fails in the meantime)
|
||||||
|
retry(3, new Runnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
// Save this synchronously to ensure all nodes read correct state
|
||||||
|
InfinispanUserSessionInitializer.this.cache.getAdvancedCache().
|
||||||
|
withFlags(Flag.IGNORE_RETURN_VALUES, Flag.FORCE_SYNCHRONOUS)
|
||||||
|
.put(stateKey, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private boolean isCoordinator() {
|
||||||
|
Transport transport = cache.getCacheManager().getTransport();
|
||||||
|
return transport == null || transport.isCoordinator();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Just coordinator is supposed to run this
|
||||||
|
private void startLoading() {
|
||||||
|
InitializerState state = getOrCreateInitializerState();
|
||||||
|
|
||||||
|
// Assume each worker has same processor's count
|
||||||
|
int processors = Runtime.getRuntime().availableProcessors();
|
||||||
|
|
||||||
|
ExecutorService localExecutor = Executors.newCachedThreadPool();
|
||||||
|
DistributedExecutorService distributedExecutorService = new DefaultExecutorService(cache, localExecutor);
|
||||||
|
|
||||||
|
int errors = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (!state.isFinished()) {
|
||||||
|
Transport transport = cache.getCacheManager().getTransport();
|
||||||
|
int nodesCount = transport==null ? 1 : transport.getMembers().size();
|
||||||
|
int distributedWorkersCount = processors * nodesCount;
|
||||||
|
|
||||||
|
// TODO: debug
|
||||||
|
log.infof("Starting next iteration with %d workers", distributedWorkersCount);
|
||||||
|
|
||||||
|
List<Integer> segments = state.getUnfinishedSegments(distributedWorkersCount);
|
||||||
|
|
||||||
|
// TODO: trace
|
||||||
|
log.info("unfinished segments for this iteration: " + segments);
|
||||||
|
|
||||||
|
List<Future<WorkerResult>> futures = new LinkedList<>();
|
||||||
|
for (Integer segment : segments) {
|
||||||
|
SessionInitializerWorker worker = new SessionInitializerWorker();
|
||||||
|
worker.setWorkerEnvironment(segment, sessionsPerSegment, sessionLoader);
|
||||||
|
|
||||||
|
Future<WorkerResult> future = distributedExecutorService.submit(worker);
|
||||||
|
futures.add(future);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Future<WorkerResult> future : futures) {
|
||||||
|
try {
|
||||||
|
WorkerResult result = future.get();
|
||||||
|
|
||||||
|
if (result.getSuccess()) {
|
||||||
|
int computedSegment = result.getSegment();
|
||||||
|
state.markSegmentFinished(computedSegment);
|
||||||
|
} else {
|
||||||
|
if (log.isTraceEnabled()) {
|
||||||
|
log.tracef("Segment %d failed to compute", result.getSegment());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (InterruptedException ie) {
|
||||||
|
errors++;
|
||||||
|
log.error("Interruped exception when computed future. Errors: " + errors, ie);
|
||||||
|
} catch (ExecutionException ee) {
|
||||||
|
errors++;
|
||||||
|
log.error("ExecutionException when computed future. Errors: " + errors, ee);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors >= maxErrors) {
|
||||||
|
throw new RuntimeException("Maximum count of worker errors occured. Limit was " + maxErrors + ". See server.log for details");
|
||||||
|
}
|
||||||
|
|
||||||
|
saveStateToCache(state);
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
log.info("New initializer state pushed. The state is: " + state.printState(false));
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
distributedExecutorService.shutdown();
|
||||||
|
localExecutor.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void retry(int retry, Runnable runnable) {
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
runnable.run();
|
||||||
|
return;
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
retry--;
|
||||||
|
if (retry == 0) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Listener
|
||||||
|
public class ViewChangeListener {
|
||||||
|
|
||||||
|
@ViewChanged
|
||||||
|
public void viewChanged(ViewChangedEvent event) {
|
||||||
|
boolean isCoordinator = isCoordinator();
|
||||||
|
// TODO:
|
||||||
|
log.info("View Changed: is coordinator: " + isCoordinator);
|
||||||
|
|
||||||
|
if (isCoordinator) {
|
||||||
|
latch.countDown();
|
||||||
|
latch = new CountDownLatch(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class WorkerResult implements Serializable {
|
||||||
|
|
||||||
|
private Integer segment;
|
||||||
|
private Boolean success;
|
||||||
|
|
||||||
|
public static WorkerResult create (Integer segment, boolean success) {
|
||||||
|
WorkerResult res = new WorkerResult();
|
||||||
|
res.setSegment(segment);
|
||||||
|
res.setSuccess(success);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getSegment() {
|
||||||
|
return segment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSegment(Integer segment) {
|
||||||
|
this.segment = segment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getSuccess() {
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSuccess(Boolean success) {
|
||||||
|
this.success = success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+108
@@ -0,0 +1,108 @@
|
|||||||
|
package org.keycloak.models.sessions.infinispan.initializer;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class InitializerState extends SessionEntity {
|
||||||
|
|
||||||
|
private static final Logger log = Logger.getLogger(InitializerState.class);
|
||||||
|
|
||||||
|
private int sessionsCount;
|
||||||
|
private List<Boolean> segments = new ArrayList<>();
|
||||||
|
|
||||||
|
|
||||||
|
public void init(int sessionsCount, int sessionsPerSegment) {
|
||||||
|
this.sessionsCount = sessionsCount;
|
||||||
|
|
||||||
|
int segmentsCount = sessionsCount / sessionsPerSegment;
|
||||||
|
if (sessionsPerSegment * segmentsCount < sessionsCount) {
|
||||||
|
segmentsCount = segmentsCount + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: trace
|
||||||
|
log.info(String.format("sessionsCount: %d, sessionsPerSegment: %d, segmentsCount: %d", sessionsCount, sessionsPerSegment, segmentsCount));
|
||||||
|
|
||||||
|
for (int i=0 ; i<segmentsCount ; i++) {
|
||||||
|
segments.add(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return true just if computation is entirely finished (all segments are true)
|
||||||
|
public boolean isFinished() {
|
||||||
|
return getNextUnfinishedSegmentFromIndex(0) == -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return next un-finished segments. It can return "segmentCount" segments or less
|
||||||
|
public List<Integer> getUnfinishedSegments(int segmentCount) {
|
||||||
|
List<Integer> result = new ArrayList<>();
|
||||||
|
boolean remaining = true;
|
||||||
|
int next=0;
|
||||||
|
while (remaining && result.size() < segmentCount) {
|
||||||
|
next = getNextUnfinishedSegmentFromIndex(next);
|
||||||
|
if (next == -1) {
|
||||||
|
remaining = false;
|
||||||
|
} else {
|
||||||
|
result.add(next);
|
||||||
|
next++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markSegmentFinished(int index) {
|
||||||
|
segments.set(index, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getNextUnfinishedSegmentFromIndex(int index) {
|
||||||
|
int segmentsSize = segments.size();
|
||||||
|
for (int i=index ; i<segmentsSize ; i++) {
|
||||||
|
Boolean entry = segments.get(i);
|
||||||
|
if (!entry) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String printState(boolean includeSegments) {
|
||||||
|
int finished = 0;
|
||||||
|
int nonFinished = 0;
|
||||||
|
List<Integer> finishedList = new ArrayList<>();
|
||||||
|
List<Integer> nonFinishedList = new ArrayList<>();
|
||||||
|
|
||||||
|
int size = segments.size();
|
||||||
|
for (int i=0 ; i<size ; i++) {
|
||||||
|
Boolean done = segments.get(i);
|
||||||
|
if (done) {
|
||||||
|
finished++;
|
||||||
|
if (includeSegments) {
|
||||||
|
finishedList.add(i);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nonFinished++;
|
||||||
|
if (includeSegments) {
|
||||||
|
nonFinishedList.add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder strBuilder = new StringBuilder("sessionsCount: " + sessionsCount)
|
||||||
|
.append(", finished segments count: " + finished)
|
||||||
|
.append(", non-finished segments count: " + nonFinished);
|
||||||
|
|
||||||
|
if (includeSegments) {
|
||||||
|
strBuilder.append(", finished segments: " + finishedList)
|
||||||
|
.append(", non-finished segments: " + nonFinishedList);
|
||||||
|
}
|
||||||
|
|
||||||
|
return strBuilder.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
package org.keycloak.models.sessions.infinispan.initializer;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.keycloak.models.ClientSessionModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
import org.keycloak.models.session.UserSessionPersisterProvider;
|
||||||
|
import org.keycloak.util.Time;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class OfflineUserSessionLoader implements SessionLoader {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSessionsCount(KeycloakSession session) {
|
||||||
|
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||||
|
return persister.getUserSessionsCount(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean loadSessions(KeycloakSession session, int first, int max) {
|
||||||
|
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||||
|
List<UserSessionModel> sessions = persister.loadUserSessions(first, max, true);
|
||||||
|
|
||||||
|
// TODO: Each worker may have different time. Improve if needed...
|
||||||
|
int currentTime = Time.currentTime();
|
||||||
|
|
||||||
|
for (UserSessionModel persistentSession : sessions) {
|
||||||
|
|
||||||
|
// Update and persist lastSessionRefresh time
|
||||||
|
persistentSession.setLastSessionRefresh(currentTime);
|
||||||
|
persister.updateUserSession(persistentSession, true);
|
||||||
|
|
||||||
|
// Save to memory/infinispan
|
||||||
|
UserSessionModel offlineUserSession = session.sessions().createOfflineUserSession(persistentSession);
|
||||||
|
|
||||||
|
for (ClientSessionModel persistentClientSession : persistentSession.getClientSessions()) {
|
||||||
|
ClientSessionModel offlineClientSession = session.sessions().createOfflineClientSession(persistentClientSession);
|
||||||
|
offlineClientSession.setUserSession(offlineUserSession);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+65
@@ -0,0 +1,65 @@
|
|||||||
|
package org.keycloak.models.sessions.infinispan.initializer;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.infinispan.Cache;
|
||||||
|
import org.infinispan.distexec.DistributedCallable;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.KeycloakSessionTask;
|
||||||
|
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class SessionInitializerWorker implements DistributedCallable<String, SessionEntity, InfinispanUserSessionInitializer.WorkerResult>, Serializable {
|
||||||
|
|
||||||
|
private static final Logger log = Logger.getLogger(SessionInitializerWorker.class);
|
||||||
|
|
||||||
|
private int segment;
|
||||||
|
private int sessionsPerSegment;
|
||||||
|
private SessionLoader sessionLoader;
|
||||||
|
|
||||||
|
private transient Cache<String, SessionEntity> cache;
|
||||||
|
|
||||||
|
public void setWorkerEnvironment(int segment, int sessionsPerSegment, SessionLoader sessionLoader) {
|
||||||
|
this.segment = segment;
|
||||||
|
this.sessionsPerSegment = sessionsPerSegment;
|
||||||
|
this.sessionLoader = sessionLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnvironment(Cache<String, SessionEntity> cache, Set<String> inputKeys) {
|
||||||
|
this.cache = cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InfinispanUserSessionInitializer.WorkerResult call() throws Exception {
|
||||||
|
// TODO
|
||||||
|
log.infof("Running computation for segment: %d", segment);
|
||||||
|
|
||||||
|
KeycloakSessionFactory sessionFactory = cache.getAdvancedCache().getComponentRegistry().getComponent(KeycloakSessionFactory.class);
|
||||||
|
if (sessionFactory == null) {
|
||||||
|
log.warnf("KeycloakSessionFactory not yet set in cache. Worker skipped");
|
||||||
|
return InfinispanUserSessionInitializer.WorkerResult.create(segment, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
final int first = segment * sessionsPerSegment;
|
||||||
|
final int max = sessionsPerSegment;
|
||||||
|
|
||||||
|
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(KeycloakSession session) {
|
||||||
|
sessionLoader.loadSessions(session, first, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
return InfinispanUserSessionInitializer.WorkerResult.create(segment, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
package org.keycloak.models.sessions.infinispan.initializer;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public interface SessionLoader extends Serializable {
|
||||||
|
|
||||||
|
int getSessionsCount(KeycloakSession session);
|
||||||
|
|
||||||
|
boolean loadSessions(KeycloakSession session, int first, int max);
|
||||||
|
}
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
package org.keycloak.models.sessions.infinispan.mapreduce;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import org.infinispan.distexec.mapreduce.Collector;
|
||||||
|
import org.infinispan.distexec.mapreduce.Mapper;
|
||||||
|
import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
|
||||||
|
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all clientSessions attached to any from input list of userSessions
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class ClientSessionsOfUserSessionMapper implements Mapper<String, SessionEntity, String, ClientSessionEntity>, Serializable {
|
||||||
|
|
||||||
|
private String realm;
|
||||||
|
private Collection<String> userSessions;
|
||||||
|
|
||||||
|
public ClientSessionsOfUserSessionMapper(String realm, Collection<String> userSessions) {
|
||||||
|
this.realm = realm;
|
||||||
|
this.userSessions = userSessions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void map(String key, SessionEntity e, Collector<String, ClientSessionEntity> collector) {
|
||||||
|
if (!realm.equals(e.getRealm())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(e instanceof ClientSessionEntity)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientSessionEntity entity = (ClientSessionEntity) e;
|
||||||
|
|
||||||
|
for (String userSessionId : userSessions) {
|
||||||
|
if (userSessionId.equals(((ClientSessionEntity) e).getUserSession())) {
|
||||||
|
collector.emit(entity.getId(), entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
package org.keycloak.models.sessions.infinispan.initializer;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class InitializerStateTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testComputationState() {
|
||||||
|
InitializerState state = new InitializerState();
|
||||||
|
state.init(28, 5);
|
||||||
|
|
||||||
|
Assert.assertFalse(state.isFinished());
|
||||||
|
List<Integer> segments = state.getUnfinishedSegments(3);
|
||||||
|
assertContains(segments, 3, 0, 1, 2);
|
||||||
|
|
||||||
|
state.markSegmentFinished(1);
|
||||||
|
state.markSegmentFinished(2);
|
||||||
|
segments = state.getUnfinishedSegments(4);
|
||||||
|
assertContains(segments, 4, 0, 3, 4, 5);
|
||||||
|
|
||||||
|
state.markSegmentFinished(0);
|
||||||
|
state.markSegmentFinished(3);
|
||||||
|
segments = state.getUnfinishedSegments(4);
|
||||||
|
assertContains(segments, 2, 4, 5);
|
||||||
|
|
||||||
|
state.markSegmentFinished(4);
|
||||||
|
state.markSegmentFinished(5);
|
||||||
|
segments = state.getUnfinishedSegments(4);
|
||||||
|
Assert.assertTrue(segments.isEmpty());
|
||||||
|
Assert.assertTrue(state.isFinished());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertContains(List<Integer> segments, int expectedLength, int... expected) {
|
||||||
|
Assert.assertEquals(segments.size(), expectedLength);
|
||||||
|
for (int i : expected) {
|
||||||
|
Assert.assertTrue(segments.contains(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,7 +32,7 @@ import org.keycloak.representations.RefreshToken;
|
|||||||
import org.keycloak.services.ErrorResponseException;
|
import org.keycloak.services.ErrorResponseException;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.services.managers.ClientSessionCode;
|
import org.keycloak.services.managers.ClientSessionCode;
|
||||||
import org.keycloak.services.offline.OfflineTokenUtils;
|
import org.keycloak.services.managers.UserSessionManager;
|
||||||
import org.keycloak.util.TokenUtil;
|
import org.keycloak.util.TokenUtil;
|
||||||
import org.keycloak.util.Time;
|
import org.keycloak.util.Time;
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ public class TokenManager {
|
|||||||
ClientSessionModel clientSession = null;
|
ClientSessionModel clientSession = null;
|
||||||
if (TokenUtil.TOKEN_TYPE_OFFLINE.equals(oldToken.getType())) {
|
if (TokenUtil.TOKEN_TYPE_OFFLINE.equals(oldToken.getType())) {
|
||||||
|
|
||||||
clientSession = OfflineTokenUtils.findOfflineClientSession(session, realm, user, oldToken.getClientSession(), oldToken.getSessionState());
|
clientSession = new UserSessionManager(session).findOfflineClientSession(realm, oldToken.getClientSession(), oldToken.getSessionState());
|
||||||
if (clientSession != null) {
|
if (clientSession != null) {
|
||||||
userSession = clientSession.getUserSession();
|
userSession = clientSession.getUserSession();
|
||||||
}
|
}
|
||||||
@@ -490,14 +490,15 @@ public class TokenManager {
|
|||||||
String scopeParam = clientSession.getNote(OIDCLoginProtocol.SCOPE_PARAM);
|
String scopeParam = clientSession.getNote(OIDCLoginProtocol.SCOPE_PARAM);
|
||||||
boolean offlineTokenRequested = TokenUtil.isOfflineTokenRequested(scopeParam);
|
boolean offlineTokenRequested = TokenUtil.isOfflineTokenRequested(scopeParam);
|
||||||
if (offlineTokenRequested) {
|
if (offlineTokenRequested) {
|
||||||
if (!OfflineTokenUtils.isOfflineTokenAllowed(realm, clientSession)) {
|
UserSessionManager sessionManager = new UserSessionManager(session);
|
||||||
|
if (!sessionManager.isOfflineTokenAllowed(clientSession)) {
|
||||||
event.error(Errors.NOT_ALLOWED);
|
event.error(Errors.NOT_ALLOWED);
|
||||||
throw new ErrorResponseException("not_allowed", "Offline tokens not allowed for the user or client", Response.Status.BAD_REQUEST);
|
throw new ErrorResponseException("not_allowed", "Offline tokens not allowed for the user or client", Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshToken = new RefreshToken(accessToken);
|
refreshToken = new RefreshToken(accessToken);
|
||||||
refreshToken.type(TokenUtil.TOKEN_TYPE_OFFLINE);
|
refreshToken.type(TokenUtil.TOKEN_TYPE_OFFLINE);
|
||||||
OfflineTokenUtils.persistOfflineSession(session, realm, clientSession, userSession);
|
sessionManager.persistOfflineSession(clientSession, userSession);
|
||||||
} else {
|
} else {
|
||||||
refreshToken = new RefreshToken(accessToken);
|
refreshToken = new RefreshToken(accessToken);
|
||||||
refreshToken.expiration(Time.currentTime() + realm.getSsoSessionIdleTimeout());
|
refreshToken.expiration(Time.currentTime() + realm.getSsoSessionIdleTimeout());
|
||||||
|
|||||||
@@ -2,16 +2,15 @@ package org.keycloak.services;
|
|||||||
|
|
||||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||||
import org.keycloak.ClientConnection;
|
import org.keycloak.ClientConnection;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.*;
|
||||||
import org.keycloak.models.KeycloakContext;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
import org.keycloak.models.RealmModel;
|
|
||||||
import org.keycloak.models.utils.RealmImporter;
|
import org.keycloak.models.utils.RealmImporter;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.services.resources.KeycloakApplication;
|
import org.keycloak.services.resources.KeycloakApplication;
|
||||||
|
import org.keycloak.services.util.LocaleHelper;
|
||||||
|
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
@@ -87,4 +86,9 @@ public class DefaultKeycloakContext implements KeycloakContext {
|
|||||||
manager.setContextPath(getContextPath());
|
manager.setContextPath(getContextPath());
|
||||||
return manager;
|
return manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Locale resolveLocale(UserModel user) {
|
||||||
|
return LocaleHelper.getLocale(session, realm, user);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -33,42 +33,24 @@ import org.keycloak.events.EventBuilder;
|
|||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.jose.jws.JWSBuilder;
|
import org.keycloak.jose.jws.JWSBuilder;
|
||||||
import org.keycloak.login.LoginFormsProvider;
|
import org.keycloak.login.LoginFormsProvider;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.*;
|
||||||
import org.keycloak.models.ClientSessionModel;
|
|
||||||
import org.keycloak.models.RequiredActionProviderModel;
|
|
||||||
import org.keycloak.models.UserConsentModel;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
import org.keycloak.models.ProtocolMapperModel;
|
|
||||||
import org.keycloak.models.RealmModel;
|
|
||||||
import org.keycloak.models.RoleModel;
|
|
||||||
import org.keycloak.models.UserModel;
|
|
||||||
import org.keycloak.models.UserSessionModel;
|
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.protocol.LoginProtocol;
|
import org.keycloak.protocol.LoginProtocol;
|
||||||
import org.keycloak.protocol.RestartLoginCookie;
|
import org.keycloak.protocol.RestartLoginCookie;
|
||||||
import org.keycloak.protocol.oidc.TokenManager;
|
import org.keycloak.protocol.oidc.TokenManager;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
|
import org.keycloak.services.Urls;
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
import org.keycloak.services.resources.IdentityBrokerService;
|
import org.keycloak.services.resources.IdentityBrokerService;
|
||||||
import org.keycloak.services.resources.RealmsResource;
|
import org.keycloak.services.resources.RealmsResource;
|
||||||
import org.keycloak.services.Urls;
|
|
||||||
import org.keycloak.services.util.CookieHelper;
|
import org.keycloak.services.util.CookieHelper;
|
||||||
import org.keycloak.util.Time;
|
import org.keycloak.util.Time;
|
||||||
|
|
||||||
import javax.ws.rs.core.Cookie;
|
import javax.ws.rs.core.*;
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
|
||||||
import javax.ws.rs.core.NewCookie;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import javax.ws.rs.core.UriInfo;
|
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import org.keycloak.freemarker.LocaleHelper;
|
|
||||||
import org.keycloak.util.TokenUtil;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stateless object that manages authentication
|
* Stateless object that manages authentication
|
||||||
@@ -413,7 +395,8 @@ public class AuthenticationManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLoginLocale(realm, userSession, request, uriInfo);
|
// Updates users locale if required
|
||||||
|
session.getContext().resolveLocale(userSession.getUser());
|
||||||
|
|
||||||
// refresh the cookies!
|
// refresh the cookies!
|
||||||
createLoginCookie(realm, userSession.getUser(), userSession, uriInfo, clientConnection);
|
createLoginCookie(realm, userSession.getUser(), userSession, uriInfo, clientConnection);
|
||||||
@@ -428,17 +411,6 @@ public class AuthenticationManager {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a locale has been set on the login screen, associate that locale with the user
|
|
||||||
private static void handleLoginLocale(RealmModel realm, UserSessionModel userSession,
|
|
||||||
HttpRequest request, UriInfo uriInfo) {
|
|
||||||
Cookie localeCookie = request.getHttpHeaders().getCookies().get(LocaleHelper.LOCALE_COOKIE);
|
|
||||||
if (localeCookie == null) return;
|
|
||||||
|
|
||||||
UserModel user = userSession.getUser();
|
|
||||||
Locale locale = LocaleHelper.getLocale(realm, user, uriInfo, request.getHttpHeaders());
|
|
||||||
user.setSingleAttribute(UserModel.LOCALE, locale.toLanguageTag());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Response nextActionAfterAuthentication(KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession,
|
public static Response nextActionAfterAuthentication(KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession,
|
||||||
ClientConnection clientConnection,
|
ClientConnection clientConnection,
|
||||||
HttpRequest request, UriInfo uriInfo, EventBuilder event) {
|
HttpRequest request, UriInfo uriInfo, EventBuilder event) {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import org.keycloak.models.ProtocolMapperModel;
|
|||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionProvider;
|
import org.keycloak.models.UserSessionProvider;
|
||||||
|
import org.keycloak.models.session.UserSessionPersisterProvider;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper;
|
import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper;
|
||||||
@@ -54,6 +55,11 @@ public class ClientManager {
|
|||||||
sessions.onClientRemoved(realm, client);
|
sessions.onClientRemoved(realm, client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UserSessionPersisterProvider sessionsPersister = realmManager.getSession().getProvider(UserSessionPersisterProvider.class);
|
||||||
|
if (sessionsPersister != null) {
|
||||||
|
sessionsPersister.onClientRemoved(realm, client);
|
||||||
|
}
|
||||||
|
|
||||||
UserModel serviceAccountUser = realmManager.getSession().users().getUserByServiceAccountClient(client);
|
UserModel serviceAccountUser = realmManager.getSession().users().getUserByServiceAccountClient(client);
|
||||||
if (serviceAccountUser != null) {
|
if (serviceAccountUser != null) {
|
||||||
realmManager.getSession().users().removeUser(realm, serviceAccountUser);
|
realmManager.getSession().users().removeUser(realm, serviceAccountUser);
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package org.keycloak.services.managers;
|
|||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.enums.SslRequired;
|
import org.keycloak.enums.SslRequired;
|
||||||
|
import org.keycloak.models.session.UserSessionPersisterProvider;
|
||||||
import org.keycloak.models.utils.RealmImporter;
|
import org.keycloak.models.utils.RealmImporter;
|
||||||
import org.keycloak.models.AccountRoles;
|
import org.keycloak.models.AccountRoles;
|
||||||
import org.keycloak.models.AdminRoles;
|
import org.keycloak.models.AdminRoles;
|
||||||
@@ -197,6 +198,11 @@ public class RealmManager implements RealmImporter {
|
|||||||
sessions.onRealmRemoved(realm);
|
sessions.onRealmRemoved(realm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UserSessionPersisterProvider sessionsPersister = session.getProvider(UserSessionPersisterProvider.class);
|
||||||
|
if (sessionsPersister != null) {
|
||||||
|
sessionsPersister.onRealmRemoved(realm);
|
||||||
|
}
|
||||||
|
|
||||||
// Remove all periodic syncs for configured federation providers
|
// Remove all periodic syncs for configured federation providers
|
||||||
UsersSyncManager usersSyncManager = new UsersSyncManager();
|
UsersSyncManager usersSyncManager = new UsersSyncManager();
|
||||||
for (final UserFederationProviderModel fedProvider : federationProviders) {
|
for (final UserFederationProviderModel fedProvider : federationProviders) {
|
||||||
@@ -210,10 +216,10 @@ public class RealmManager implements RealmImporter {
|
|||||||
realm.setEventsEnabled(rep.isEventsEnabled());
|
realm.setEventsEnabled(rep.isEventsEnabled());
|
||||||
realm.setEventsExpiration(rep.getEventsExpiration() != null ? rep.getEventsExpiration() : 0);
|
realm.setEventsExpiration(rep.getEventsExpiration() != null ? rep.getEventsExpiration() : 0);
|
||||||
if (rep.getEventsListeners() != null) {
|
if (rep.getEventsListeners() != null) {
|
||||||
realm.setEventsListeners(new HashSet<String>(rep.getEventsListeners()));
|
realm.setEventsListeners(new HashSet<>(rep.getEventsListeners()));
|
||||||
}
|
}
|
||||||
if(rep.getEnabledEventTypes() != null) {
|
if(rep.getEnabledEventTypes() != null) {
|
||||||
realm.setEnabledEventTypes(new HashSet<String>(rep.getEnabledEventTypes()));
|
realm.setEnabledEventTypes(new HashSet<>(rep.getEnabledEventTypes()));
|
||||||
}
|
}
|
||||||
|
|
||||||
realm.setAdminEventsEnabled(rep.isAdminEventsEnabled());
|
realm.setAdminEventsEnabled(rep.isAdminEventsEnabled());
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import org.keycloak.models.KeycloakSession;
|
|||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionProvider;
|
import org.keycloak.models.UserSessionProvider;
|
||||||
|
import org.keycloak.models.session.UserSessionPersisterProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
@@ -21,6 +22,12 @@ public class UserManager {
|
|||||||
if (sessions != null) {
|
if (sessions != null) {
|
||||||
sessions.onUserRemoved(realm, user);
|
sessions.onUserRemoved(realm, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UserSessionPersisterProvider sessionsPersister = session.getProvider(UserSessionPersisterProvider.class);
|
||||||
|
if (sessionsPersister != null) {
|
||||||
|
sessionsPersister.onUserRemoved(realm, user);
|
||||||
|
}
|
||||||
|
|
||||||
if (session.users().removeUser(realm, user)) {
|
if (session.users().removeUser(realm, user)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
package org.keycloak.services.managers;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.ClientSessionModel;
|
||||||
|
import org.keycloak.models.Constants;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.ModelException;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
import org.keycloak.models.session.UserSessionPersisterProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class UserSessionManager {
|
||||||
|
|
||||||
|
protected static Logger logger = Logger.getLogger(UserSessionManager.class);
|
||||||
|
|
||||||
|
private final KeycloakSession kcSession;
|
||||||
|
private final UserSessionPersisterProvider persister;
|
||||||
|
|
||||||
|
public UserSessionManager(KeycloakSession session) {
|
||||||
|
this.kcSession = session;
|
||||||
|
this.persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void persistOfflineSession(ClientSessionModel clientSession, UserSessionModel userSession) {
|
||||||
|
UserModel user = userSession.getUser();
|
||||||
|
|
||||||
|
// Verify if we already have UserSession with this ID. If yes, don't create another one
|
||||||
|
UserSessionModel offlineUserSession = kcSession.sessions().getOfflineUserSession(clientSession.getRealm(), userSession.getId());
|
||||||
|
if (offlineUserSession == null) {
|
||||||
|
offlineUserSession = createOfflineUserSession(user, userSession);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create clientSession and save to DB.
|
||||||
|
createOfflineClientSession(user, clientSession, offlineUserSession);
|
||||||
|
}
|
||||||
|
|
||||||
|
// userSessionId is provided from offline token. It's used just to verify if it match the ID from clientSession representation
|
||||||
|
public ClientSessionModel findOfflineClientSession(RealmModel realm, String clientSessionId, String userSessionId) {
|
||||||
|
ClientSessionModel clientSession = kcSession.sessions().getOfflineClientSession(realm, clientSessionId);
|
||||||
|
if (clientSession == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!userSessionId.equals(clientSession.getUserSession().getId())) {
|
||||||
|
throw new ModelException("User session don't match. Offline client session " + clientSession.getId() + ", It's user session " + clientSession.getUserSession().getId() +
|
||||||
|
" Wanted user session: " + userSessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<ClientModel> findClientsWithOfflineToken(RealmModel realm, UserModel user) {
|
||||||
|
List<ClientSessionModel> clientSessions = kcSession.sessions().getOfflineClientSessions(realm, user);
|
||||||
|
Set<ClientModel> clients = new HashSet<>();
|
||||||
|
for (ClientSessionModel clientSession : clientSessions) {
|
||||||
|
clients.add(clientSession.getClient());
|
||||||
|
}
|
||||||
|
return clients;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean revokeOfflineToken(UserModel user, ClientModel client) {
|
||||||
|
RealmModel realm = client.getRealm();
|
||||||
|
|
||||||
|
List<ClientSessionModel> clientSessions = kcSession.sessions().getOfflineClientSessions(realm, user);
|
||||||
|
boolean anyRemoved = false;
|
||||||
|
for (ClientSessionModel clientSession : clientSessions) {
|
||||||
|
if (clientSession.getClient().getId().equals(client.getId())) {
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.tracef("Removing existing offline token for user '%s' and client '%s' . ClientSessionID was '%s' .",
|
||||||
|
user.getUsername(), client.getClientId(), clientSession.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
kcSession.sessions().removeOfflineClientSession(realm, clientSession.getId());
|
||||||
|
persister.removeClientSession(clientSession.getId(), true);
|
||||||
|
checkOfflineUserSessionHasClientSessions(realm, user, clientSession.getUserSession().getId(), clientSessions);
|
||||||
|
anyRemoved = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return anyRemoved;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOfflineTokenAllowed(ClientSessionModel clientSession) {
|
||||||
|
RoleModel offlineAccessRole = clientSession.getRealm().getRole(Constants.OFFLINE_ACCESS_ROLE);
|
||||||
|
if (offlineAccessRole == null) {
|
||||||
|
logger.warnf("Role '%s' not available in realm", Constants.OFFLINE_ACCESS_ROLE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientSession.getRoles().contains(offlineAccessRole.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private UserSessionModel createOfflineUserSession(UserModel user, UserSessionModel userSession) {
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.tracef("Creating new offline user session. UserSessionID: '%s' , Username: '%s'", userSession.getId(), user.getUsername());
|
||||||
|
}
|
||||||
|
|
||||||
|
UserSessionModel offlineUserSession = kcSession.sessions().createOfflineUserSession(userSession);
|
||||||
|
persister.createUserSession(userSession, true);
|
||||||
|
return offlineUserSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createOfflineClientSession(UserModel user, ClientSessionModel clientSession, UserSessionModel userSession) {
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.tracef("Creating new offline token client session. ClientSessionId: '%s', UserSessionID: '%s' , Username: '%s', Client: '%s'" ,
|
||||||
|
clientSession.getId(), userSession.getId(), user.getUsername(), clientSession.getClient().getClientId());
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientSessionModel offlineClientSession = kcSession.sessions().createOfflineClientSession(clientSession);
|
||||||
|
offlineClientSession.setUserSession(userSession);
|
||||||
|
persister.createClientSession(clientSession, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if userSession has any offline clientSessions attached to it. Remove userSession if not
|
||||||
|
private void checkOfflineUserSessionHasClientSessions(RealmModel realm, UserModel user, String userSessionId, List<ClientSessionModel> clientSessions) {
|
||||||
|
for (ClientSessionModel clientSession : clientSessions) {
|
||||||
|
if (clientSession.getUserSession().getId().equals(userSessionId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.tracef("Removing offline userSession for user %s as it doesn't have any client sessions attached. UserSessionID: %s", user.getUsername(), userSessionId);
|
||||||
|
}
|
||||||
|
kcSession.sessions().removeOfflineUserSession(realm, userSessionId);
|
||||||
|
persister.removeUserSession(userSessionId, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
-300
@@ -1,300 +0,0 @@
|
|||||||
package org.keycloak.services.offline;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.HashMap;
|
|
||||||
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.OfflineClientSessionModel;
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class OfflineClientSessionAdapter implements ClientSessionModel {
|
|
||||||
|
|
||||||
private final OfflineClientSessionModel model;
|
|
||||||
private final RealmModel realm;
|
|
||||||
private final ClientModel client;
|
|
||||||
private final OfflineUserSessionAdapter userSession;
|
|
||||||
|
|
||||||
private OfflineClientSessionData data;
|
|
||||||
|
|
||||||
public OfflineClientSessionAdapter(OfflineClientSessionModel model, RealmModel realm, ClientModel client, OfflineUserSessionAdapter userSession) {
|
|
||||||
this.model = model;
|
|
||||||
this.realm = realm;
|
|
||||||
this.client = client;
|
|
||||||
this.userSession = userSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
// lazily init representation
|
|
||||||
private OfflineClientSessionData getData() {
|
|
||||||
if (data == null) {
|
|
||||||
try {
|
|
||||||
data = JsonSerialization.readValue(model.getData(), OfflineClientSessionData.class);
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
throw new ModelException(ioe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
@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) {
|
|
||||||
throw new IllegalStateException("Not supported setUserSession");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getRedirectUri() {
|
|
||||||
return getData().getRedirectUri();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setRedirectUri(String uri) {
|
|
||||||
throw new IllegalStateException("Not supported setRedirectUri");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getTimestamp() {
|
|
||||||
return getData().getTimestamp();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setTimestamp(int timestamp) {
|
|
||||||
throw new IllegalStateException("Not supported setTimestamp");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getAction() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setAction(String action) {
|
|
||||||
throw new IllegalStateException("Not supported setAction");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<String> getRoles() {
|
|
||||||
return getData().getRoles();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setRoles(Set<String> roles) {
|
|
||||||
throw new IllegalStateException("Not supported setRoles");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<String> getProtocolMappers() {
|
|
||||||
return getData().getProtocolMappers();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setProtocolMappers(Set<String> protocolMappers) {
|
|
||||||
throw new IllegalStateException("Not supported setProtocolMappers");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, ExecutionStatus> getExecutionStatus() {
|
|
||||||
return getData().getAuthenticatorStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setExecutionStatus(String authenticator, ExecutionStatus status) {
|
|
||||||
throw new IllegalStateException("Not supported setExecutionStatus");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clearExecutionStatus() {
|
|
||||||
throw new IllegalStateException("Not supported clearExecutionStatus");
|
|
||||||
}
|
|
||||||
|
|
||||||
@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) {
|
|
||||||
throw new IllegalStateException("Not supported setAuthMethod");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getNote(String name) {
|
|
||||||
return getData().getNotes()==null ? null : getData().getNotes().get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setNote(String name, String value) {
|
|
||||||
throw new IllegalStateException("Not supported setNote");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeNote(String name) {
|
|
||||||
throw new IllegalStateException("Not supported removeNote");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, String> getNotes() {
|
|
||||||
return getData().getNotes();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<String> getRequiredActions() {
|
|
||||||
throw new IllegalStateException("Not supported getRequiredActions");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addRequiredAction(String action) {
|
|
||||||
throw new IllegalStateException("Not supported addRequiredAction");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeRequiredAction(String action) {
|
|
||||||
throw new IllegalStateException("Not supported removeRequiredAction");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addRequiredAction(UserModel.RequiredAction action) {
|
|
||||||
throw new IllegalStateException("Not supported addRequiredAction");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeRequiredAction(UserModel.RequiredAction action) {
|
|
||||||
throw new IllegalStateException("Not supported removeRequiredAction");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setUserSessionNote(String name, String value) {
|
|
||||||
throw new IllegalStateException("Not supported setUserSessionNote");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, String> getUserSessionNotes() {
|
|
||||||
throw new IllegalStateException("Not supported getUserSessionNotes");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clearUserSessionNotes() {
|
|
||||||
throw new IllegalStateException("Not supported clearUserSessionNotes");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static class OfflineClientSessionData {
|
|
||||||
|
|
||||||
@JsonProperty("authMethod")
|
|
||||||
private String authMethod;
|
|
||||||
|
|
||||||
@JsonProperty("redirectUri")
|
|
||||||
private String redirectUri;
|
|
||||||
|
|
||||||
@JsonProperty("protocolMappers")
|
|
||||||
private Set<String> protocolMappers;
|
|
||||||
|
|
||||||
@JsonProperty("roles")
|
|
||||||
private Set<String> roles;
|
|
||||||
|
|
||||||
@JsonProperty("notes")
|
|
||||||
private Map<String, String> notes;
|
|
||||||
|
|
||||||
@JsonProperty("authenticatorStatus")
|
|
||||||
private Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus = new HashMap<>();
|
|
||||||
|
|
||||||
@JsonProperty("timestamp")
|
|
||||||
private int timestamp;
|
|
||||||
|
|
||||||
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<String> getProtocolMappers() {
|
|
||||||
return protocolMappers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setProtocolMappers(Set<String> protocolMappers) {
|
|
||||||
this.protocolMappers = protocolMappers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<String> getRoles() {
|
|
||||||
return roles;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRoles(Set<String> roles) {
|
|
||||||
this.roles = roles;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, String> getNotes() {
|
|
||||||
return notes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setNotes(Map<String, String> notes) {
|
|
||||||
this.notes = notes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, ClientSessionModel.ExecutionStatus> getAuthenticatorStatus() {
|
|
||||||
return authenticatorStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAuthenticatorStatus(Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus) {
|
|
||||||
this.authenticatorStatus = authenticatorStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getTimestamp() {
|
|
||||||
return timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTimestamp(int timestamp) {
|
|
||||||
this.timestamp = timestamp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loaded 100 of 122 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user