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>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</project>
|
||||
+13
@@ -66,6 +66,19 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
||||
} else {
|
||||
initEmbedded();
|
||||
}
|
||||
|
||||
// Backwards compatibility
|
||||
if (cacheManager.getCacheConfiguration(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME) == null) {
|
||||
logger.warnf("No configuration provided for '%s' cache. Using '%s' configuration as template",
|
||||
InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, InfinispanConnectionProvider.SESSION_CACHE_NAME);
|
||||
|
||||
Configuration sessionCacheConfig = cacheManager.getCacheConfiguration(InfinispanConnectionProvider.SESSION_CACHE_NAME);
|
||||
if (sessionCacheConfig != null) {
|
||||
ConfigurationBuilder confBuilder = new ConfigurationBuilder().read(sessionCacheConfig);
|
||||
Configuration offlineSessionConfig = confBuilder.build();
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, offlineSessionConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -11,6 +11,7 @@ public interface InfinispanConnectionProvider extends Provider {
|
||||
static final String REALM_CACHE_NAME = "realms";
|
||||
static final String USER_CACHE_NAME = "users";
|
||||
static final String SESSION_CACHE_NAME = "sessions";
|
||||
static final String OFFLINE_SESSION_CACHE_NAME = "offlineSessions";
|
||||
static final String LOGIN_FAILURE_CACHE_NAME = "loginFailures";
|
||||
|
||||
<K, V> Cache<K, V> getCache(String name);
|
||||
|
||||
@@ -13,19 +13,23 @@
|
||||
</addColumn>
|
||||
|
||||
<createTable tableName="OFFLINE_USER_SESSION">
|
||||
<column name="USER_SESSION_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="USER_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</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"/>
|
||||
</column>
|
||||
<column name="DATA" type="CLOB"/>
|
||||
</createTable>
|
||||
|
||||
<createTable tableName="OFFLINE_CLIENT_SESSION">
|
||||
<column name="USER_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="CLIENT_SESSION_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
@@ -35,14 +39,13 @@
|
||||
<column name="CLIENT_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="OFFLINE" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="DATA" type="CLOB"/>
|
||||
</createTable>
|
||||
|
||||
<addPrimaryKey columnNames="USER_SESSION_ID" 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"/>
|
||||
<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"/>
|
||||
|
||||
<addPrimaryKey columnNames="USER_SESSION_ID, OFFLINE" constraintName="CONSTRAINT_OFFLINE_US_SES_PK" tableName="OFFLINE_USER_SESSION"/>
|
||||
<addPrimaryKey columnNames="CLIENT_SESSION_ID, OFFLINE" constraintName="CONSTRAINT_OFFLINE_CL_SES_PK" tableName="OFFLINE_CLIENT_SESSION"/>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
@@ -29,8 +29,8 @@
|
||||
<class>org.keycloak.models.jpa.entities.AuthenticationExecutionEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.AuthenticatorConfigEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.RequiredActionProviderEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.OfflineUserSessionEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.OfflineClientSessionEntity</class>
|
||||
<class>org.keycloak.models.jpa.session.PersistentUserSessionEntity</class>
|
||||
<class>org.keycloak.models.jpa.session.PersistentClientSessionEntity</class>
|
||||
|
||||
<!-- JpaAuditProviders -->
|
||||
<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.MongoClient;
|
||||
import com.mongodb.MongoClientOptions;
|
||||
import com.mongodb.MongoClientURI;
|
||||
import com.mongodb.MongoCredential;
|
||||
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.MongoUserConsentEntity",
|
||||
"org.keycloak.models.mongo.keycloak.entities.MongoMigrationModelEntity",
|
||||
"org.keycloak.models.mongo.keycloak.entities.MongoOnlineUserSessionEntity",
|
||||
"org.keycloak.models.mongo.keycloak.entities.MongoOfflineUserSessionEntity",
|
||||
"org.keycloak.models.entities.IdentityProviderEntity",
|
||||
"org.keycloak.models.entities.ClientIdentityProviderMappingEntity",
|
||||
"org.keycloak.models.entities.RequiredCredentialEntity",
|
||||
@@ -48,8 +51,8 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
|
||||
"org.keycloak.models.entities.AuthenticationFlowEntity",
|
||||
"org.keycloak.models.entities.AuthenticatorConfigEntity",
|
||||
"org.keycloak.models.entities.RequiredActionProviderEntity",
|
||||
"org.keycloak.models.entities.OfflineUserSessionEntity",
|
||||
"org.keycloak.models.entities.OfflineClientSessionEntity",
|
||||
"org.keycloak.models.entities.PersistentUserSessionEntity",
|
||||
"org.keycloak.models.entities.PersistentClientSessionEntity",
|
||||
};
|
||||
|
||||
private static final Logger logger = Logger.getLogger(DefaultMongoConnectionFactoryProvider.class);
|
||||
@@ -147,31 +150,55 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
|
||||
* @throws UnknownHostException
|
||||
*/
|
||||
protected MongoClient createMongoClient() throws UnknownHostException {
|
||||
String host = config.get("host", ServerAddress.defaultHost());
|
||||
int port = config.getInt("port", ServerAddress.defaultPort());
|
||||
operationalInfo = new LinkedHashMap<>();
|
||||
String dbName = config.get("db", "keycloak");
|
||||
|
||||
String user = config.get("user");
|
||||
String password = config.get("password");
|
||||
String uriString = config.get("uri");
|
||||
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;
|
||||
if (user != null && password != null) {
|
||||
MongoCredential credential = MongoCredential.createMongoCRCredential(user, dbName, password.toCharArray());
|
||||
client = new MongoClient(new ServerAddress(host, port), Collections.singletonList(credential), clientOptions);
|
||||
operationalInfo.put("mongoHosts", hosts);
|
||||
operationalInfo.put("mongoDatabaseName", dbName);
|
||||
operationalInfo.put("mongoUser", uri.getUsername());
|
||||
operationalInfo.put("mongoDriverVersion", client.getVersion());
|
||||
|
||||
logger.debugv("Initialized mongo model. host(s): %s, db: %s", uri.getHosts(), dbName);
|
||||
return client;
|
||||
} 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() {
|
||||
|
||||
@@ -41,6 +41,9 @@ public interface OAuth2Constants {
|
||||
// http://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
|
||||
String OFFLINE_ACCESS = "offline_access";
|
||||
|
||||
String UI_LOCALES_PARAM = "ui_locales";
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
+4
@@ -22,6 +22,10 @@
|
||||
"provider": "jpa"
|
||||
},
|
||||
|
||||
"userSessionPersister": {
|
||||
"provider": "jpa"
|
||||
},
|
||||
|
||||
"timer": {
|
||||
"provider": "basic"
|
||||
},
|
||||
|
||||
@@ -20,6 +20,13 @@
|
||||
<include>**/**</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
<!--<fileSet>
|
||||
<directory>cli</directory>
|
||||
<includes>
|
||||
<include>*.cli</include>
|
||||
</includes>
|
||||
<outputDirectory>bin</outputDirectory>
|
||||
</fileSet>-->
|
||||
</fileSets>
|
||||
|
||||
<files>
|
||||
@@ -45,14 +52,6 @@
|
||||
<source>src/main/providers/README.txt</source>
|
||||
<outputDirectory>standalone/configuration/providers</outputDirectory>
|
||||
</file>
|
||||
<file>
|
||||
<source>cli/keycloak-prepare.cli</source>
|
||||
<outputDirectory>bin</outputDirectory>
|
||||
</file>
|
||||
<file>
|
||||
<source>cli/keycloak-install.cli</source>
|
||||
<outputDirectory>bin</outputDirectory>
|
||||
</file>
|
||||
</files>
|
||||
|
||||
</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=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>
|
||||
<outputDirectory></outputDirectory>
|
||||
</fileSet>
|
||||
|
||||
<!--<fileSet>
|
||||
<directory>cli</directory>
|
||||
<includes>
|
||||
<include>*.cli</include>
|
||||
</includes>
|
||||
<outputDirectory>bin</outputDirectory>
|
||||
</fileSet>-->
|
||||
</fileSets>
|
||||
|
||||
<files>
|
||||
@@ -65,10 +71,6 @@
|
||||
<source>${project.build.directory}/unpacked/keycloak-${project.version}/standalone/configuration/keycloak-server.json</source>
|
||||
<outputDirectory>standalone/configuration</outputDirectory>
|
||||
</file>
|
||||
<file>
|
||||
<source>cli/keycloak-install.cli</source>
|
||||
<outputDirectory>bin</outputDirectory>
|
||||
</file>
|
||||
</files>
|
||||
|
||||
</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)
|
||||
/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
|
||||
enter in a new password.
|
||||
</para>
|
||||
<para>
|
||||
<!--<para>
|
||||
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>
|
||||
</para>
|
||||
</para>-->
|
||||
</section>
|
||||
<section>
|
||||
<title>Install on existing JBoss EAP 6.4.0.GA</title>
|
||||
<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>.
|
||||
</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>
|
||||
<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>
|
||||
for details about those options and their default values.
|
||||
</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>
|
||||
|
||||
+23
-24
@@ -1,7 +1,7 @@
|
||||
package org.keycloak.exportimport.util;
|
||||
|
||||
import org.keycloak.models.OfflineClientSessionModel;
|
||||
import org.keycloak.models.OfflineUserSessionModel;
|
||||
import org.keycloak.models.session.PersistentClientSessionModel;
|
||||
import org.keycloak.models.session.PersistentUserSessionModel;
|
||||
import org.keycloak.representations.idm.OfflineUserSessionRepresentation;
|
||||
import org.keycloak.util.Base64;
|
||||
import org.codehaus.jackson.JsonEncoding;
|
||||
@@ -11,7 +11,6 @@ import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.codehaus.jackson.map.SerializationConfig;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleContainerModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
@@ -298,27 +297,27 @@ public class ExportUtils {
|
||||
}
|
||||
}
|
||||
|
||||
// Offline sessions
|
||||
List<OfflineUserSessionRepresentation> offlineSessionReps = new LinkedList<>();
|
||||
Collection<OfflineUserSessionModel> offlineSessions = session.users().getOfflineUserSessions(realm, user);
|
||||
Collection<OfflineClientSessionModel> offlineClientSessions = session.users().getOfflineClientSessions(realm, user);
|
||||
|
||||
Map<String, List<OfflineClientSessionModel>> processed = new HashMap<>();
|
||||
for (OfflineClientSessionModel clsm : offlineClientSessions) {
|
||||
String userSessionId = clsm.getUserSessionId();
|
||||
List<OfflineClientSessionModel> current = processed.get(userSessionId);
|
||||
if (current == null) {
|
||||
current = new LinkedList<>();
|
||||
processed.put(userSessionId, current);
|
||||
}
|
||||
current.add(clsm);
|
||||
}
|
||||
|
||||
for (OfflineUserSessionModel userSession : offlineSessions) {
|
||||
OfflineUserSessionRepresentation sessionRep = ModelToRepresentation.toRepresentation(realm, userSession, processed.get(userSession.getUserSessionId()));
|
||||
offlineSessionReps.add(sessionRep);
|
||||
}
|
||||
userRep.setOfflineUserSessions(offlineSessionReps);
|
||||
// // Offline sessions
|
||||
// List<OfflineUserSessionRepresentation> offlineSessionReps = new LinkedList<>();
|
||||
// Collection<PersistentUserSessionModel> offlineSessions = session.users().getOfflineUserSessions(realm, user);
|
||||
// Collection<PersistentClientSessionModel> offlineClientSessions = session.users().getOfflineClientSessions(realm, user);
|
||||
//
|
||||
// Map<String, List<PersistentClientSessionModel>> processed = new HashMap<>();
|
||||
// for (PersistentClientSessionModel clsm : offlineClientSessions) {
|
||||
// String userSessionId = clsm.getUserSessionId();
|
||||
// List<PersistentClientSessionModel> current = processed.get(userSessionId);
|
||||
// if (current == null) {
|
||||
// current = new LinkedList<>();
|
||||
// processed.put(userSessionId, current);
|
||||
// }
|
||||
// current.add(clsm);
|
||||
// }
|
||||
//
|
||||
// for (PersistentUserSessionModel userSession : offlineSessions) {
|
||||
// OfflineUserSessionRepresentation sessionRep = ModelToRepresentation.toRepresentation(realm, userSession, processed.get(userSession.getUserSessionId()));
|
||||
// offlineSessionReps.add(sessionRep);
|
||||
// }
|
||||
// userRep.setOfflineUserSessions(offlineSessionReps);
|
||||
|
||||
return userRep;
|
||||
}
|
||||
|
||||
+1
-7
@@ -51,7 +51,6 @@ import org.keycloak.events.Event;
|
||||
import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
|
||||
import org.keycloak.freemarker.FreeMarkerException;
|
||||
import org.keycloak.freemarker.FreeMarkerUtil;
|
||||
import org.keycloak.freemarker.LocaleHelper;
|
||||
import org.keycloak.freemarker.Theme;
|
||||
import org.keycloak.freemarker.ThemeProvider;
|
||||
import org.keycloak.freemarker.beans.AdvancedMessageFormatterMethod;
|
||||
@@ -65,7 +64,6 @@ import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.utils.FormMessage;
|
||||
import org.keycloak.services.Urls;
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
|
||||
Locale locale = LocaleHelper.getLocale(realm, user, uriInfo, headers);
|
||||
Locale locale = session.getContext().resolveLocale(user);
|
||||
Properties messagesBundle;
|
||||
try {
|
||||
messagesBundle = theme.getMessages(locale);
|
||||
@@ -213,10 +211,6 @@ public class FreeMarkerAccountProvider implements AccountProvider {
|
||||
String result = freeMarker.processTemplate(attributes, Templates.getTemplate(page), theme);
|
||||
Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML).entity(result);
|
||||
BrowserSecurityHeaderSetup.headers(builder, realm);
|
||||
|
||||
String keycloakLocaleCookiePath = Urls.localeCookiePath(baseUri, realm.getName());
|
||||
|
||||
LocaleHelper.updateLocaleCookie(builder, locale, realm, uriInfo, keycloakLocaleCookiePath);
|
||||
return builder.build();
|
||||
} catch (FreeMarkerException 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.UserModel;
|
||||
import org.keycloak.protocol.oidc.TokenManager;
|
||||
import org.keycloak.services.offline.OfflineTokenUtils;
|
||||
import org.keycloak.services.managers.UserSessionManager;
|
||||
import org.keycloak.util.MultivaluedHashMap;
|
||||
|
||||
/**
|
||||
@@ -25,7 +25,7 @@ public class ApplicationsBean {
|
||||
|
||||
public ApplicationsBean(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||
|
||||
Set<ClientModel> offlineClients = OfflineTokenUtils.findClientsWithOfflineToken(session, realm, user);
|
||||
Set<ClientModel> offlineClients = new UserSessionManager(session).findClientsWithOfflineToken(realm, user);
|
||||
|
||||
List<ClientModel> realmClients = realm.getClients();
|
||||
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;
|
||||
|
||||
import org.keycloak.freemarker.LocaleHelper;
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
@@ -22,7 +21,7 @@ public class LocaleBean {
|
||||
supported = new LinkedList<>();
|
||||
for (String l : realm.getSupportedLocales()) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
return;
|
||||
}
|
||||
var result = input.substring(0, 1).toUpperCase();
|
||||
var s = input.substring(1);
|
||||
for (var i=0; i<s.length ; i++) {
|
||||
var c = s[i];
|
||||
if (c.match(/[A-Z]/)) {
|
||||
result = result.concat(" ")
|
||||
};
|
||||
result = result.concat(c);
|
||||
var splittedWords = input.split(/\s+/);
|
||||
for (var i=0; i<splittedWords.length ; i++) {
|
||||
splittedWords[i] = splittedWords[i].charAt(0).toUpperCase() + splittedWords[i].slice(1);
|
||||
};
|
||||
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..."/>
|
||||
</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 class="form-group" data-ng-show="access.manageEvents && eventsConfig.eventsEnabled">
|
||||
|
||||
@@ -29,6 +29,11 @@
|
||||
<artifactId>keycloak-model-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-services</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-events-api</artifactId>
|
||||
|
||||
+1
-2
@@ -24,7 +24,6 @@ import org.keycloak.events.Event;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.freemarker.FreeMarkerException;
|
||||
import org.keycloak.freemarker.FreeMarkerUtil;
|
||||
import org.keycloak.freemarker.LocaleHelper;
|
||||
import org.keycloak.freemarker.Theme;
|
||||
import org.keycloak.freemarker.ThemeProvider;
|
||||
import org.keycloak.freemarker.beans.MessageFormatterMethod;
|
||||
@@ -110,7 +109,7 @@ public class FreeMarkerEmailProvider implements EmailProvider {
|
||||
try {
|
||||
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
|
||||
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);
|
||||
Properties rb = theme.getMessages(locale);
|
||||
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.FreeMarkerException;
|
||||
import org.keycloak.freemarker.FreeMarkerUtil;
|
||||
import org.keycloak.freemarker.LocaleHelper;
|
||||
import org.keycloak.freemarker.Theme;
|
||||
import org.keycloak.freemarker.ThemeProvider;
|
||||
import org.keycloak.freemarker.beans.AdvancedMessageFormatterMethod;
|
||||
@@ -201,7 +200,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||
}
|
||||
|
||||
Properties messagesBundle;
|
||||
Locale locale = LocaleHelper.getLocale(realm, user, uriInfo, session.getContext().getRequestHeaders());
|
||||
Locale locale = session.getContext().resolveLocale(user);
|
||||
try {
|
||||
messagesBundle = theme.getMessages(locale);
|
||||
attributes.put("msg", new MessageFormatterMethod(locale, messagesBundle));
|
||||
@@ -292,10 +291,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||
for (Map.Entry<String, String> entry : httpResponseHeaders.entrySet()) {
|
||||
builder.header(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
String cookiePath = Urls.localeCookiePath(baseUri, realm.getName());
|
||||
LocaleHelper.updateLocaleCookie(builder, locale, realm, uriInfo, cookiePath);
|
||||
|
||||
return builder.build();
|
||||
} catch (FreeMarkerException e) {
|
||||
logger.error("Failed to process template", e);
|
||||
@@ -345,7 +340,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||
}
|
||||
|
||||
Properties messagesBundle;
|
||||
Locale locale = LocaleHelper.getLocale(realm, user, uriInfo, session.getContext().getRequestHeaders());
|
||||
Locale locale = session.getContext().resolveLocale(user);
|
||||
try {
|
||||
messagesBundle = theme.getMessages(locale);
|
||||
attributes.put("msg", new MessageFormatterMethod(locale, messagesBundle));
|
||||
@@ -393,9 +388,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||
for (Map.Entry<String, String> entry : httpResponseHeaders.entrySet()) {
|
||||
builder.header(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
String cookiePath = Urls.localeCookiePath(baseUri, realm.getName());
|
||||
LocaleHelper.updateLocaleCookie(builder, locale, realm, uriInfo, cookiePath);
|
||||
return builder.build();
|
||||
} catch (FreeMarkerException 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.UriInfo;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
@@ -33,4 +34,6 @@ public interface KeycloakContext {
|
||||
|
||||
RealmImporter getRealmManager();
|
||||
|
||||
Locale resolveLocale(UserModel user);
|
||||
|
||||
}
|
||||
@@ -97,6 +97,9 @@ public interface RealmModel extends RoleContainerModel {
|
||||
int getSsoSessionMaxLifespan();
|
||||
void setSsoSessionMaxLifespan(int seconds);
|
||||
|
||||
// int getOfflineSessionIdleTimeout();
|
||||
// void setOfflineSessionIdleTimeout(int seconds);
|
||||
|
||||
int getAccessTokenLifespan();
|
||||
|
||||
void setAccessTokenLifespan(int seconds);
|
||||
@@ -286,6 +289,10 @@ public interface RealmModel extends RoleContainerModel {
|
||||
|
||||
void setEventsEnabled(boolean enabled);
|
||||
|
||||
// boolean isPersistUserSessions();
|
||||
//
|
||||
// void setPersistUserSessions();
|
||||
|
||||
long getEventsExpiration();
|
||||
|
||||
void setEventsExpiration(long expiration);
|
||||
|
||||
@@ -475,70 +475,6 @@ public class UserFederationManager implements UserProvider {
|
||||
return (result != null) ? result : CredentialValidationOutput.failed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOfflineUserSession(RealmModel realm, UserModel user, OfflineUserSessionModel offlineUserSession) {
|
||||
validateUser(realm, user);
|
||||
if (user == null) throw new IllegalStateException("Federated user no longer valid");
|
||||
session.userStorage().addOfflineUserSession(realm, user, offlineUserSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OfflineUserSessionModel getOfflineUserSession(RealmModel realm, UserModel user, String userSessionId) {
|
||||
validateUser(realm, user);
|
||||
if (user == null) throw new IllegalStateException("Federated user no longer valid");
|
||||
return session.userStorage().getOfflineUserSession(realm, user, userSessionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<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
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.keycloak.models;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -10,11 +9,11 @@ import java.util.Set;
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface UserModel {
|
||||
public static final String USERNAME = "username";
|
||||
public static final String LAST_NAME = "lastName";
|
||||
public static final String FIRST_NAME = "firstName";
|
||||
public static final String EMAIL = "email";
|
||||
public static final String LOCALE = "locale";
|
||||
String USERNAME = "username";
|
||||
String LAST_NAME = "lastName";
|
||||
String FIRST_NAME = "firstName";
|
||||
String EMAIL = "email";
|
||||
String LOCALE = "locale";
|
||||
|
||||
String getId();
|
||||
|
||||
|
||||
@@ -56,17 +56,5 @@ public interface UserProvider extends Provider {
|
||||
boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input);
|
||||
CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel... input);
|
||||
|
||||
void addOfflineUserSession(RealmModel realm, UserModel user, OfflineUserSessionModel offlineUserSession);
|
||||
OfflineUserSessionModel getOfflineUserSession(RealmModel realm, UserModel user, String userSessionId);
|
||||
Collection<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();
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import java.util.Map;
|
||||
public interface UserSessionModel {
|
||||
|
||||
String getId();
|
||||
RealmModel getRealm();
|
||||
|
||||
/**
|
||||
* If created via a broker external login, this is an identifier that can be
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.keycloak.models;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -27,6 +28,8 @@ public interface UserSessionProvider extends Provider {
|
||||
int getActiveUserSessions(RealmModel realm, ClientModel client);
|
||||
void removeUserSession(RealmModel realm, UserSessionModel session);
|
||||
void removeUserSessions(RealmModel realm, UserModel user);
|
||||
|
||||
// Implementation should propagate removal of expired userSessions to userSessionPersister too
|
||||
void removeExpiredUserSessions(RealmModel realm);
|
||||
void removeUserSessions(RealmModel realm);
|
||||
void removeClientSession(RealmModel realm, ClientSessionModel clientSession);
|
||||
@@ -40,6 +43,22 @@ public interface UserSessionProvider extends Provider {
|
||||
void onClientRemoved(RealmModel realm, ClientModel client);
|
||||
void onUserRemoved(RealmModel realm, UserModel user);
|
||||
|
||||
UserSessionModel createOfflineUserSession(UserSessionModel userSession);
|
||||
UserSessionModel getOfflineUserSession(RealmModel realm, String userSessionId);
|
||||
|
||||
// Removes the attached clientSessions as well
|
||||
void removeOfflineUserSession(RealmModel realm, String userSessionId);
|
||||
|
||||
ClientSessionModel createOfflineClientSession(ClientSessionModel clientSession);
|
||||
ClientSessionModel getOfflineClientSession(RealmModel realm, String clientSessionId);
|
||||
List<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();
|
||||
|
||||
}
|
||||
@@ -7,4 +7,8 @@ import org.keycloak.provider.ProviderFactory;
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
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>
|
||||
*/
|
||||
public class OfflineClientSessionEntity {
|
||||
public class PersistentClientSessionEntity {
|
||||
|
||||
private String clientSessionId;
|
||||
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 String federationLink;
|
||||
private String serviceAccountClientLink;
|
||||
private List<OfflineUserSessionEntity> offlineUserSessions;
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
@@ -158,13 +157,5 @@ public class UserEntity extends AbstractIdentifiableEntity {
|
||||
public void setServiceAccountClientLink(String serviceAccountClientLink) {
|
||||
this.serviceAccountClientLink = serviceAccountClientLink;
|
||||
}
|
||||
|
||||
public List<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>
|
||||
*/
|
||||
public class OfflineClientSessionModel {
|
||||
public class PersistentClientSessionModel {
|
||||
|
||||
private String clientSessionId;
|
||||
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.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.codehaus.jackson.annotate.JsonProperty;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.OfflineUserSessionModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
@@ -15,23 +16,47 @@ import org.keycloak.util.JsonSerialization;
|
||||
/**
|
||||
* @author <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 RealmModel realm;
|
||||
private final List<ClientSessionModel> clientSessions;
|
||||
|
||||
private OfflineUserSessionData data;
|
||||
private PersistentUserSessionData data;
|
||||
|
||||
public OfflineUserSessionAdapter(OfflineUserSessionModel model, UserModel user) {
|
||||
this.model = model;
|
||||
this.user = user;
|
||||
public PersistentUserSessionAdapter(UserSessionModel other) {
|
||||
this.data = new PersistentUserSessionData();
|
||||
data.setAuthMethod(other.getAuthMethod());
|
||||
data.setBrokerSessionId(other.getBrokerSessionId());
|
||||
data.setBrokerUserId(other.getBrokerUserId());
|
||||
data.setIpAddress(other.getIpAddress());
|
||||
data.setNotes(other.getNotes());
|
||||
data.setRememberMe(other.isRememberMe());
|
||||
data.setStarted(other.getStarted());
|
||||
data.setState(other.getState());
|
||||
|
||||
this.model = new PersistentUserSessionModel();
|
||||
this.model.setUserSessionId(other.getId());
|
||||
this.model.setLastSessionRefresh(other.getLastSessionRefresh());
|
||||
|
||||
this.user = other.getUser();
|
||||
this.realm = other.getRealm();
|
||||
this.clientSessions = other.getClientSessions();
|
||||
}
|
||||
|
||||
// lazily init representation
|
||||
private OfflineUserSessionData getData() {
|
||||
public PersistentUserSessionAdapter(PersistentUserSessionModel model, RealmModel realm, UserModel user, List<ClientSessionModel> clientSessions) {
|
||||
this.model = model;
|
||||
this.realm = realm;
|
||||
this.user = user;
|
||||
this.clientSessions = clientSessions;
|
||||
}
|
||||
|
||||
// Lazily init data
|
||||
private PersistentUserSessionData getData() {
|
||||
if (data == null) {
|
||||
try {
|
||||
data = JsonSerialization.readValue(model.getData(), OfflineUserSessionData.class);
|
||||
data = JsonSerialization.readValue(model.getData(), PersistentUserSessionData.class);
|
||||
} catch (IOException ioe) {
|
||||
throw new ModelException(ioe);
|
||||
}
|
||||
@@ -40,6 +65,18 @@ public class OfflineUserSessionAdapter implements UserSessionModel {
|
||||
return data;
|
||||
}
|
||||
|
||||
// Write updated model with latest serialized data
|
||||
public PersistentUserSessionModel getUpdatedModel() {
|
||||
try {
|
||||
String updatedData = JsonSerialization.writeValueAsString(getData());
|
||||
this.model.setData(updatedData);
|
||||
} catch (IOException ioe) {
|
||||
throw new ModelException(ioe);
|
||||
}
|
||||
|
||||
return this.model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return model.getUserSessionId();
|
||||
@@ -60,6 +97,11 @@ public class OfflineUserSessionAdapter implements UserSessionModel {
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel getRealm() {
|
||||
return realm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLoginUsername() {
|
||||
return user.getUsername();
|
||||
@@ -87,17 +129,17 @@ public class OfflineUserSessionAdapter implements UserSessionModel {
|
||||
|
||||
@Override
|
||||
public int getLastSessionRefresh() {
|
||||
return 0;
|
||||
return model.getLastSessionRefresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastSessionRefresh(int seconds) {
|
||||
// Ignore
|
||||
model.setLastSessionRefresh(seconds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ClientSessionModel> getClientSessions() {
|
||||
throw new IllegalStateException("Not yet supported");
|
||||
return clientSessions;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -107,13 +149,19 @@ public class OfflineUserSessionAdapter implements UserSessionModel {
|
||||
|
||||
@Override
|
||||
public void setNote(String name, String value) {
|
||||
throw new IllegalStateException("Illegal to set note offline session");
|
||||
PersistentUserSessionData data = getData();
|
||||
if (data.getNotes() == null) {
|
||||
data.setNotes(new HashMap<String, String>());
|
||||
}
|
||||
data.getNotes().put(name, value);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeNote(String name) {
|
||||
throw new IllegalStateException("Illegal to remove note from offline session");
|
||||
if (getData().getNotes() != null) {
|
||||
getData().getNotes().remove(name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -123,16 +171,29 @@ public class OfflineUserSessionAdapter implements UserSessionModel {
|
||||
|
||||
@Override
|
||||
public State getState() {
|
||||
return null;
|
||||
return getData().getState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setState(State state) {
|
||||
throw new IllegalStateException("Illegal to set state on offline session");
|
||||
getData().setState(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || !(o instanceof UserSessionModel)) return false;
|
||||
|
||||
protected static class OfflineUserSessionData {
|
||||
UserSessionModel that = (UserSessionModel) o;
|
||||
return that.getId().equals(getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getId().hashCode();
|
||||
}
|
||||
|
||||
protected static class PersistentUserSessionData {
|
||||
|
||||
@JsonProperty("brokerSessionId")
|
||||
private String brokerSessionId;
|
||||
@@ -155,6 +216,9 @@ public class OfflineUserSessionAdapter implements UserSessionModel {
|
||||
@JsonProperty("notes")
|
||||
private Map<String, String> notes;
|
||||
|
||||
@JsonProperty("state")
|
||||
private State state;
|
||||
|
||||
public String getBrokerSessionId() {
|
||||
return brokerSessionId;
|
||||
}
|
||||
@@ -211,5 +275,12 @@ public class OfflineUserSessionAdapter implements UserSessionModel {
|
||||
this.notes = notes;
|
||||
}
|
||||
|
||||
public State getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(State state) {
|
||||
this.state = state;
|
||||
}
|
||||
}
|
||||
}
|
||||
+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>
|
||||
*/
|
||||
public class OfflineUserSessionModel {
|
||||
public class PersistentUserSessionModel {
|
||||
|
||||
private String userSessionId;
|
||||
private int lastSessionRefresh;
|
||||
|
||||
private String data;
|
||||
|
||||
public String getUserSessionId() {
|
||||
@@ -16,6 +18,15 @@ public class OfflineUserSessionModel {
|
||||
this.userSessionId = userSessionId;
|
||||
}
|
||||
|
||||
public int getLastSessionRefresh() {
|
||||
return lastSessionRefresh;
|
||||
}
|
||||
|
||||
public void setLastSessionRefresh(int lastSessionRefresh) {
|
||||
this.lastSessionRefresh = lastSessionRefresh;
|
||||
}
|
||||
|
||||
|
||||
public String getData() {
|
||||
return data;
|
||||
}
|
||||
@@ -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.ModelException;
|
||||
import org.keycloak.models.OTPPolicy;
|
||||
import org.keycloak.models.OfflineClientSessionModel;
|
||||
import org.keycloak.models.OfflineUserSessionModel;
|
||||
import org.keycloak.models.session.PersistentClientSessionModel;
|
||||
import org.keycloak.models.session.PersistentUserSessionModel;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RequiredActionProviderModel;
|
||||
@@ -250,11 +250,11 @@ public class ModelToRepresentation {
|
||||
}
|
||||
|
||||
if (realm.getEventsListeners() != null) {
|
||||
rep.setEventsListeners(new LinkedList<String>(realm.getEventsListeners()));
|
||||
rep.setEventsListeners(new LinkedList<>(realm.getEventsListeners()));
|
||||
}
|
||||
|
||||
if(realm.getEnabledEventTypes() != null) {
|
||||
rep.setEnabledEventTypes(new LinkedList<String>(realm.getEnabledEventTypes()));
|
||||
rep.setEnabledEventTypes(new LinkedList<>(realm.getEnabledEventTypes()));
|
||||
}
|
||||
|
||||
rep.setAdminEventsEnabled(realm.isAdminEventsEnabled());
|
||||
@@ -511,13 +511,13 @@ public class ModelToRepresentation {
|
||||
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();
|
||||
rep.setData(model.getData());
|
||||
rep.setUserSessionId(model.getUserSessionId());
|
||||
|
||||
List<OfflineClientSessionRepresentation> clientSessionReps = new LinkedList<>();
|
||||
for (OfflineClientSessionModel clsm : clientSessions) {
|
||||
for (PersistentClientSessionModel clsm : clientSessions) {
|
||||
OfflineClientSessionRepresentation clrep = toRepresentation(realm, clsm);
|
||||
clientSessionReps.add(clrep);
|
||||
}
|
||||
@@ -525,7 +525,7 @@ public class ModelToRepresentation {
|
||||
return rep;
|
||||
}
|
||||
|
||||
public static OfflineClientSessionRepresentation toRepresentation(RealmModel realm, OfflineClientSessionModel model) {
|
||||
public static OfflineClientSessionRepresentation toRepresentation(RealmModel realm, PersistentClientSessionModel model) {
|
||||
OfflineClientSessionRepresentation rep = new OfflineClientSessionRepresentation();
|
||||
|
||||
String clientInternalId = model.getClientId();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.keycloak.models.utils;
|
||||
|
||||
import org.keycloak.models.OfflineClientSessionModel;
|
||||
import org.keycloak.models.OfflineUserSessionModel;
|
||||
import org.keycloak.models.session.PersistentClientSessionModel;
|
||||
import org.keycloak.models.session.PersistentUserSessionModel;
|
||||
import org.keycloak.representations.idm.OfflineClientSessionRepresentation;
|
||||
import org.keycloak.representations.idm.OfflineUserSessionRepresentation;
|
||||
import org.keycloak.util.Base64;
|
||||
@@ -985,11 +985,6 @@ public class RepresentationToModel {
|
||||
user.addConsent(consentModel);
|
||||
}
|
||||
}
|
||||
if (userRep.getOfflineUserSessions() != null) {
|
||||
for (OfflineUserSessionRepresentation sessionRep : userRep.getOfflineUserSessions()) {
|
||||
importOfflineSession(session, newRealm, user, sessionRep);
|
||||
}
|
||||
}
|
||||
if (userRep.getServiceAccountClientId() != null) {
|
||||
String clientId = userRep.getServiceAccountClientId();
|
||||
ClientModel client = clientMap.get(clientId);
|
||||
@@ -1160,28 +1155,29 @@ public class RepresentationToModel {
|
||||
return consentModel;
|
||||
}
|
||||
|
||||
public static void importOfflineSession(KeycloakSession session, RealmModel newRealm, UserModel user, OfflineUserSessionRepresentation sessionRep) {
|
||||
OfflineUserSessionModel model = new OfflineUserSessionModel();
|
||||
model.setUserSessionId(sessionRep.getUserSessionId());
|
||||
model.setData(sessionRep.getData());
|
||||
session.users().addOfflineUserSession(newRealm, user, model);
|
||||
|
||||
for (OfflineClientSessionRepresentation csRep : sessionRep.getOfflineClientSessions()) {
|
||||
OfflineClientSessionModel csModel = new OfflineClientSessionModel();
|
||||
String clientId = csRep.getClient();
|
||||
ClientModel client = newRealm.getClientByClientId(clientId);
|
||||
if (client == null) {
|
||||
throw new RuntimeException("Unable to find client " + clientId + " referenced from offlineClientSession of user " + user.getUsername());
|
||||
}
|
||||
csModel.setClientId(client.getId());
|
||||
csModel.setUserId(user.getId());
|
||||
csModel.setClientSessionId(csRep.getClientSessionId());
|
||||
csModel.setUserSessionId(sessionRep.getUserSessionId());
|
||||
csModel.setData(csRep.getData());
|
||||
|
||||
session.users().addOfflineClientSession(newRealm, csModel);
|
||||
}
|
||||
}
|
||||
// TODO
|
||||
// public static void importOfflineSession(KeycloakSession session, RealmModel newRealm, UserModel user, OfflineUserSessionRepresentation sessionRep) {
|
||||
// PersistentUserSessionModel model = new PersistentUserSessionModel();
|
||||
// model.setUserSessionId(sessionRep.getUserSessionId());
|
||||
// model.setData(sessionRep.getData());
|
||||
// session.users().createOfflineUserSession(newRealm, user, model);
|
||||
//
|
||||
// for (OfflineClientSessionRepresentation csRep : sessionRep.getOfflineClientSessions()) {
|
||||
// PersistentClientSessionModel csModel = new PersistentClientSessionModel();
|
||||
// String clientId = csRep.getClient();
|
||||
// ClientModel client = newRealm.getClientByClientId(clientId);
|
||||
// if (client == null) {
|
||||
// throw new RuntimeException("Unable to find client " + clientId + " referenced from offlineClientSession of user " + user.getUsername());
|
||||
// }
|
||||
// csModel.setClientId(client.getId());
|
||||
// csModel.setUserId(user.getId());
|
||||
// csModel.setClientSessionId(csRep.getClientSessionId());
|
||||
// csModel.setUserSessionId(sessionRep.getUserSessionId());
|
||||
// csModel.setData(csRep.getData());
|
||||
//
|
||||
// session.users().createOfflineClientSession(newRealm, csModel);
|
||||
// }
|
||||
// }
|
||||
|
||||
public static AuthenticationFlowModel toModel(AuthenticationFlowRepresentation rep) {
|
||||
AuthenticationFlowModel model = new AuthenticationFlowModel();
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
package org.keycloak.models.utils;
|
||||
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.OfflineClientSessionModel;
|
||||
import org.keycloak.models.OfflineUserSessionModel;
|
||||
import org.keycloak.models.UserConsentModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserCredentialValueModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.keycloak.models.session.DisabledUserSessionPersisterProvider
|
||||
@@ -3,4 +3,5 @@ org.keycloak.mappers.UserFederationMapperSpi
|
||||
org.keycloak.models.RealmSpi
|
||||
org.keycloak.models.UserSessionSpi
|
||||
org.keycloak.models.UserSpi
|
||||
org.keycloak.models.session.UserSessionPersisterSpi
|
||||
org.keycloak.migration.MigrationSpi
|
||||
@@ -24,8 +24,8 @@ import org.keycloak.models.FederatedIdentityModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.OfflineClientSessionModel;
|
||||
import org.keycloak.models.OfflineUserSessionModel;
|
||||
import org.keycloak.models.session.PersistentClientSessionModel;
|
||||
import org.keycloak.models.session.PersistentUserSessionModel;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RequiredActionProviderModel;
|
||||
@@ -35,8 +35,8 @@ import org.keycloak.models.UserFederationProviderModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserProvider;
|
||||
import org.keycloak.models.entities.FederatedIdentityEntity;
|
||||
import org.keycloak.models.entities.OfflineClientSessionEntity;
|
||||
import org.keycloak.models.entities.OfflineUserSessionEntity;
|
||||
import org.keycloak.models.entities.PersistentClientSessionEntity;
|
||||
import org.keycloak.models.entities.PersistentUserSessionEntity;
|
||||
import org.keycloak.models.entities.UserEntity;
|
||||
import org.keycloak.models.file.adapter.UserAdapter;
|
||||
import org.keycloak.models.utils.CredentialValidation;
|
||||
@@ -494,188 +494,4 @@ public class FileUserProvider implements UserProvider {
|
||||
//throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
|
||||
return null; // not supported yet
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOfflineUserSession(RealmModel realm, UserModel userModel, OfflineUserSessionModel userSession) {
|
||||
userModel = getUserById(userModel.getId(), realm);
|
||||
UserEntity userEntity = ((UserAdapter) userModel).getUserEntity();
|
||||
|
||||
if (userEntity.getOfflineUserSessions() == null) {
|
||||
userEntity.setOfflineUserSessions(new ArrayList<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 org.keycloak.models.ModelDuplicateException;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.OTPPolicy;
|
||||
import org.keycloak.models.OfflineClientSessionModel;
|
||||
import org.keycloak.models.OfflineUserSessionModel;
|
||||
import org.keycloak.models.UserConsentModel;
|
||||
import org.keycloak.models.PasswordPolicy;
|
||||
import org.keycloak.models.RealmModel;
|
||||
@@ -35,8 +32,6 @@ import org.keycloak.models.UserCredentialValueModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.entities.CredentialEntity;
|
||||
import org.keycloak.models.entities.FederatedIdentityEntity;
|
||||
import org.keycloak.models.entities.OfflineClientSessionEntity;
|
||||
import org.keycloak.models.entities.OfflineUserSessionEntity;
|
||||
import org.keycloak.models.entities.RoleEntity;
|
||||
import org.keycloak.models.entities.UserEntity;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
@@ -44,7 +39,6 @@ import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
|
||||
import org.keycloak.util.Time;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
@@ -53,8 +47,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt;
|
||||
|
||||
/**
|
||||
* UserModel for JSON persistence.
|
||||
*
|
||||
|
||||
+5
-93
@@ -4,6 +4,8 @@ import org.keycloak.models.*;
|
||||
import org.keycloak.models.cache.CacheUserProvider;
|
||||
import org.keycloak.models.cache.UserCache;
|
||||
import org.keycloak.models.cache.entities.CachedUser;
|
||||
import org.keycloak.models.session.PersistentClientSessionModel;
|
||||
import org.keycloak.models.session.PersistentUserSessionModel;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -121,7 +123,7 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
|
||||
if (model == null) return null;
|
||||
if (managedUsers.containsKey(id)) return managedUsers.get(id);
|
||||
if (userInvalidations.containsKey(id)) return model;
|
||||
cached = new CachedUser(this, realm, model);
|
||||
cached = new CachedUser(realm, model);
|
||||
cache.addCachedUser(realm.getId(), cached);
|
||||
} else if (managedUsers.containsKey(id)) {
|
||||
return managedUsers.get(id);
|
||||
@@ -146,7 +148,7 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
|
||||
if (model == null) return null;
|
||||
if (managedUsers.containsKey(model.getId())) return managedUsers.get(model.getId());
|
||||
if (userInvalidations.containsKey(model.getId())) return model;
|
||||
cached = new CachedUser(this, realm, model);
|
||||
cached = new CachedUser(realm, model);
|
||||
cache.addCachedUser(realm.getId(), cached);
|
||||
} else if (userInvalidations.containsKey(cached.getId())) {
|
||||
return getDelegate().getUserById(cached.getId(), realm);
|
||||
@@ -173,7 +175,7 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
|
||||
UserModel model = getDelegate().getUserByEmail(email, realm);
|
||||
if (model == null) return null;
|
||||
if (userInvalidations.containsKey(model.getId())) return model;
|
||||
cached = new CachedUser(this, realm, model);
|
||||
cached = new CachedUser(realm, model);
|
||||
cache.addCachedUser(realm.getId(), cached);
|
||||
} else if (userInvalidations.containsKey(cached.getId())) {
|
||||
return getDelegate().getUserByEmail(email, realm);
|
||||
@@ -328,94 +330,4 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
|
||||
public void preRemove(ClientModel client, ProtocolMapperModel protocolMapper) {
|
||||
getDelegate().preRemove(client, protocolMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOfflineUserSession(RealmModel realm, UserModel user, OfflineUserSessionModel offlineUserSession) {
|
||||
registerUserInvalidation(realm, user.getId());
|
||||
getDelegate().addOfflineUserSession(realm, user, offlineUserSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OfflineUserSessionModel getOfflineUserSession(RealmModel realm, UserModel user, String userSessionId) {
|
||||
if (isRegisteredForInvalidation(realm, user.getId())) {
|
||||
return getDelegate().getOfflineUserSession(realm, user, userSessionId);
|
||||
}
|
||||
|
||||
CachedUser cachedUser = cache.getCachedUser(realm.getId(), user.getId());
|
||||
if (cachedUser == null) {
|
||||
return getDelegate().getOfflineUserSession(realm, user, userSessionId);
|
||||
} else {
|
||||
return cachedUser.getOfflineUserSessions().get(userSessionId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<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;
|
||||
|
||||
import org.keycloak.models.OfflineClientSessionModel;
|
||||
import org.keycloak.models.OfflineUserSessionModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserCredentialValueModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.cache.CacheUserProvider;
|
||||
import org.keycloak.util.MultivaluedHashMap;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -30,18 +25,16 @@ public class CachedUser implements Serializable {
|
||||
private String lastName;
|
||||
private String email;
|
||||
private boolean emailVerified;
|
||||
private List<UserCredentialValueModel> credentials = new LinkedList<UserCredentialValueModel>();
|
||||
private List<UserCredentialValueModel> credentials = new LinkedList<>();
|
||||
private boolean enabled;
|
||||
private boolean totp;
|
||||
private String federationLink;
|
||||
private String serviceAccountClientLink;
|
||||
private MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
|
||||
private Set<String> requiredActions = new HashSet<>();
|
||||
private Set<String> roleMappings = new HashSet<String>();
|
||||
private Map<String, OfflineUserSessionModel> offlineUserSessions = new HashMap<>();
|
||||
private Map<String, OfflineClientSessionModel> offlineClientSessions = new HashMap<>();
|
||||
private Set<String> roleMappings = new HashSet<>();
|
||||
|
||||
public CachedUser(CacheUserProvider cacheUserProvider, RealmModel realm, UserModel user) {
|
||||
public CachedUser(RealmModel realm, UserModel user) {
|
||||
this.id = user.getId();
|
||||
this.realm = realm.getId();
|
||||
this.username = user.getUsername();
|
||||
@@ -60,12 +53,6 @@ public class CachedUser implements Serializable {
|
||||
for (RoleModel role : user.getRoleMappings()) {
|
||||
roleMappings.add(role.getId());
|
||||
}
|
||||
for (OfflineUserSessionModel offlineSession : cacheUserProvider.getDelegate().getOfflineUserSessions(realm, user)) {
|
||||
offlineUserSessions.put(offlineSession.getUserSessionId(), offlineSession);
|
||||
}
|
||||
for (OfflineClientSessionModel offlineSession : cacheUserProvider.getDelegate().getOfflineClientSessions(realm, user)) {
|
||||
offlineClientSessions.put(offlineSession.getClientSessionId(), offlineSession);
|
||||
}
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
@@ -131,12 +118,4 @@ public class CachedUser implements Serializable {
|
||||
public String getServiceAccountClientLink() {
|
||||
return serviceAccountClientLink;
|
||||
}
|
||||
|
||||
public Map<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.FederatedIdentityModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.OfflineClientSessionModel;
|
||||
import org.keycloak.models.OfflineUserSessionModel;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RequiredActionProviderModel;
|
||||
@@ -15,21 +13,15 @@ import org.keycloak.models.UserFederationProviderModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserProvider;
|
||||
import org.keycloak.models.jpa.entities.FederatedIdentityEntity;
|
||||
import org.keycloak.models.jpa.entities.OfflineClientSessionEntity;
|
||||
import org.keycloak.models.jpa.entities.OfflineUserSessionEntity;
|
||||
import org.keycloak.models.jpa.entities.UserAttributeEntity;
|
||||
import org.keycloak.models.jpa.entities.UserEntity;
|
||||
import org.keycloak.models.utils.CredentialValidation;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.Query;
|
||||
import javax.persistence.TypedQuery;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -175,10 +167,6 @@ public class JpaUserProvider implements UserProvider {
|
||||
.setParameter("realmId", realm.getId()).executeUpdate();
|
||||
num = em.createNamedQuery("deleteUserAttributesByRealm")
|
||||
.setParameter("realmId", realm.getId()).executeUpdate();
|
||||
num = em.createNamedQuery("deleteOfflineClientSessionsByRealm")
|
||||
.setParameter("realmId", realm.getId()).executeUpdate();
|
||||
num = em.createNamedQuery("deleteOfflineUserSessionsByRealm")
|
||||
.setParameter("realmId", realm.getId()).executeUpdate();
|
||||
num = em.createNamedQuery("deleteUsersByRealm")
|
||||
.setParameter("realmId", realm.getId()).executeUpdate();
|
||||
}
|
||||
@@ -205,14 +193,6 @@ public class JpaUserProvider implements UserProvider {
|
||||
.setParameter("realmId", realm.getId())
|
||||
.setParameter("link", link.getId())
|
||||
.executeUpdate();
|
||||
num = em.createNamedQuery("deleteOfflineClientSessionsByRealmAndLink")
|
||||
.setParameter("realmId", realm.getId())
|
||||
.setParameter("link", link.getId())
|
||||
.executeUpdate();
|
||||
num = em.createNamedQuery("deleteOfflineUserSessionsByRealmAndLink")
|
||||
.setParameter("realmId", realm.getId())
|
||||
.setParameter("link", link.getId())
|
||||
.executeUpdate();
|
||||
num = em.createNamedQuery("deleteUsersByRealmAndLink")
|
||||
.setParameter("realmId", realm.getId())
|
||||
.setParameter("link", link.getId())
|
||||
@@ -230,8 +210,6 @@ public class JpaUserProvider implements UserProvider {
|
||||
em.createNamedQuery("deleteUserConsentProtMappersByClient").setParameter("clientId", client.getId()).executeUpdate();
|
||||
em.createNamedQuery("deleteUserConsentRolesByClient").setParameter("clientId", client.getId()).executeUpdate();
|
||||
em.createNamedQuery("deleteUserConsentsByClient").setParameter("clientId", client.getId()).executeUpdate();
|
||||
em.createNamedQuery("deleteOfflineClientSessionsByClient").setParameter("clientId", client.getId()).executeUpdate();
|
||||
em.createNamedQuery("deleteDetachedOfflineUserSessions").executeUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -479,167 +457,4 @@ public class JpaUserProvider implements UserProvider {
|
||||
// Not supported yet
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOfflineUserSession(RealmModel realm, UserModel user, OfflineUserSessionModel offlineUserSession) {
|
||||
UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
|
||||
|
||||
OfflineUserSessionEntity entity = new OfflineUserSessionEntity();
|
||||
entity.setUser(userEntity);
|
||||
entity.setUserSessionId(offlineUserSession.getUserSessionId());
|
||||
entity.setData(offlineUserSession.getData());
|
||||
em.persist(entity);
|
||||
userEntity.getOfflineUserSessions().add(entity);
|
||||
em.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OfflineUserSessionModel getOfflineUserSession(RealmModel realm, UserModel user, String userSessionId) {
|
||||
UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
|
||||
|
||||
for (OfflineUserSessionEntity entity : userEntity.getOfflineUserSessions()) {
|
||||
if (entity.getUserSessionId().equals(userSessionId)) {
|
||||
return toModel(entity);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private OfflineUserSessionModel toModel(OfflineUserSessionEntity entity) {
|
||||
OfflineUserSessionModel model = new OfflineUserSessionModel();
|
||||
model.setUserSessionId(entity.getUserSessionId());
|
||||
model.setData(entity.getData());
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<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.OTPPolicy;
|
||||
import org.keycloak.models.OfflineClientSessionModel;
|
||||
import org.keycloak.models.OfflineUserSessionModel;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.UserConsentModel;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
@@ -16,8 +14,6 @@ import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserCredentialValueModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.jpa.entities.CredentialEntity;
|
||||
import org.keycloak.models.jpa.entities.OfflineClientSessionEntity;
|
||||
import org.keycloak.models.jpa.entities.OfflineUserSessionEntity;
|
||||
import org.keycloak.models.jpa.entities.UserConsentEntity;
|
||||
import org.keycloak.models.jpa.entities.UserConsentProtocolMapperEntity;
|
||||
import org.keycloak.models.jpa.entities.UserConsentRoleEntity;
|
||||
@@ -37,11 +33,8 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
-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 javax.persistence.CascadeType;
|
||||
import javax.persistence.CollectionTable;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.ElementCollection;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.MapKeyColumn;
|
||||
import javax.persistence.NamedQueries;
|
||||
import javax.persistence.NamedQuery;
|
||||
import javax.persistence.OneToMany;
|
||||
@@ -18,8 +14,6 @@ import javax.persistence.UniqueConstraint;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
@@ -89,12 +83,6 @@ public class UserEntity {
|
||||
@Column(name="SERVICE_ACCOUNT_CLIENT_LINK")
|
||||
protected String serviceAccountClientLink;
|
||||
|
||||
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="user")
|
||||
protected Collection<OfflineUserSessionEntity> offlineUserSessions = new ArrayList<>();
|
||||
|
||||
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="user")
|
||||
protected Collection<OfflineClientSessionEntity> offlineClientSessions = new ArrayList<>();
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
@@ -224,22 +212,6 @@ public class UserEntity {
|
||||
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
|
||||
public boolean equals(Object o) {
|
||||
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.FederatedIdentityModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.OfflineClientSessionModel;
|
||||
import org.keycloak.models.OfflineUserSessionModel;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RequiredActionProviderModel;
|
||||
@@ -23,17 +19,13 @@ import org.keycloak.models.UserFederationProviderModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserProvider;
|
||||
import org.keycloak.models.entities.FederatedIdentityEntity;
|
||||
import org.keycloak.models.entities.OfflineClientSessionEntity;
|
||||
import org.keycloak.models.entities.OfflineUserSessionEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
|
||||
import org.keycloak.models.utils.CredentialValidation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -407,36 +399,6 @@ public class MongoUserProvider implements UserProvider {
|
||||
.and("clientId").is(client.getId())
|
||||
.get();
|
||||
getMongoStore().removeEntities(MongoUserConsentEntity.class, query, false, invocationContext);
|
||||
|
||||
// Remove all offlineClientSessions
|
||||
query = new QueryBuilder()
|
||||
.and("offlineUserSessions.offlineClientSessions.clientId").is(client.getId())
|
||||
.get();
|
||||
List<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
|
||||
@@ -482,205 +444,4 @@ public class MongoUserProvider implements UserProvider {
|
||||
// Not supported yet
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOfflineUserSession(RealmModel realm, UserModel userModel, OfflineUserSessionModel userSession) {
|
||||
MongoUserEntity user = getUserById(userModel.getId(), realm).getUser();
|
||||
|
||||
if (user.getOfflineUserSessions() == null) {
|
||||
user.setOfflineUserSessions(new ArrayList<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.models.ClientModel;
|
||||
import org.keycloak.models.OTPPolicy;
|
||||
import org.keycloak.models.OfflineClientSessionModel;
|
||||
import org.keycloak.models.OfflineUserSessionModel;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.UserConsentModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
@@ -22,19 +20,15 @@ import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserCredentialValueModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.entities.CredentialEntity;
|
||||
import org.keycloak.models.entities.OfflineClientSessionEntity;
|
||||
import org.keycloak.models.entities.OfflineUserSessionEntity;
|
||||
import org.keycloak.models.entities.UserConsentEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
|
||||
import org.keycloak.models.mongo.utils.MongoModelUtils;
|
||||
import org.keycloak.models.utils.HmacOTP;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
|
||||
import org.keycloak.util.Time;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
|
||||
+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>
|
||||
<artifactId>infinispan-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
+13
-7
@@ -26,13 +26,16 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
||||
private Cache<String, SessionEntity> cache;
|
||||
private RealmModel realm;
|
||||
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.provider = provider;
|
||||
this.cache = cache;
|
||||
this.realm = realm;
|
||||
this.entity = entity;
|
||||
this.offline = offline;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -51,8 +54,8 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserSessionModel getUserSession() {
|
||||
return entity.getUserSession() != null ? provider.getUserSession(realm, entity.getUserSession()) : null;
|
||||
public UserSessionAdapter getUserSession() {
|
||||
return entity.getUserSession() != null ? provider.getUserSession(realm, entity.getUserSession(), offline) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -63,14 +66,15 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
||||
}
|
||||
entity.setUserSession(null);
|
||||
} else {
|
||||
UserSessionAdapter userSessionAdapter = (UserSessionAdapter) userSession;
|
||||
if (entity.getUserSession() != null) {
|
||||
if (entity.getUserSession().equals(userSession.getId())) {
|
||||
return;
|
||||
} else {
|
||||
provider.dettachSession(userSession, this);
|
||||
provider.dettachSession(userSessionAdapter, this);
|
||||
}
|
||||
} else {
|
||||
provider.attachSession(userSession, this);
|
||||
provider.attachSession(userSessionAdapter, this);
|
||||
}
|
||||
|
||||
entity.setUserSession(userSession.getId());
|
||||
@@ -113,7 +117,8 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
||||
|
||||
@Override
|
||||
public Set<String> getRoles() {
|
||||
return entity.getRoles();
|
||||
if (entity.getRoles() == null || entity.getRoles().isEmpty()) return Collections.emptySet();
|
||||
return new HashSet<>(entity.getRoles());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -124,7 +129,8 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
||||
|
||||
@Override
|
||||
public Set<String> getProtocolMappers() {
|
||||
return entity.getProtocolMappers();
|
||||
if (entity.getProtocolMappers() == null || entity.getProtocolMappers().isEmpty()) return Collections.emptySet();
|
||||
return new HashSet<>(entity.getProtocolMappers());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+267
-50
@@ -7,6 +7,7 @@ import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakTransaction;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
@@ -18,6 +19,7 @@ import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
|
||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.mapreduce.ClientSessionMapper;
|
||||
import org.keycloak.models.sessions.infinispan.mapreduce.ClientSessionsOfUserSessionMapper;
|
||||
import org.keycloak.models.sessions.infinispan.mapreduce.FirstResultReducer;
|
||||
import org.keycloak.models.sessions.infinispan.mapreduce.LargestResultReducer;
|
||||
import org.keycloak.models.sessions.infinispan.mapreduce.SessionMapper;
|
||||
@@ -36,6 +38,7 @@ import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
@@ -46,18 +49,25 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||
|
||||
private final KeycloakSession session;
|
||||
private final Cache<String, SessionEntity> sessionCache;
|
||||
private final Cache<String, SessionEntity> offlineSessionCache;
|
||||
private final Cache<LoginFailureKey, LoginFailureEntity> loginFailureCache;
|
||||
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.sessionCache = sessionCache;
|
||||
this.offlineSessionCache = offlineSessionCache;
|
||||
this.loginFailureCache = loginFailureCache;
|
||||
this.tx = new InfinispanKeycloakTransaction();
|
||||
|
||||
session.getTransaction().enlistAfterCompletion(tx);
|
||||
}
|
||||
|
||||
protected Cache<String, SessionEntity> getCache(boolean offline) {
|
||||
return offline ? offlineSessionCache : sessionCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSessionModel createClientSession(RealmModel realm, ClientModel client) {
|
||||
String id = KeycloakModelUtils.generateId();
|
||||
@@ -70,7 +80,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||
|
||||
tx.put(sessionCache, id, entity);
|
||||
|
||||
return wrap(realm, entity);
|
||||
return wrap(realm, entity, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -95,29 +105,57 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||
|
||||
tx.put(sessionCache, id, entity);
|
||||
|
||||
return wrap(realm, entity);
|
||||
return wrap(realm, entity, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSessionModel getClientSession(RealmModel realm, String id) {
|
||||
ClientSessionEntity entity = (ClientSessionEntity) sessionCache.get(id);
|
||||
return wrap(realm, entity);
|
||||
return getClientSession(realm, id, false);
|
||||
}
|
||||
|
||||
protected ClientSessionModel getClientSession(RealmModel realm, String id, boolean offline) {
|
||||
Cache<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
|
||||
public ClientSessionModel getClientSession(String id) {
|
||||
ClientSessionEntity entity = (ClientSessionEntity) sessionCache.get(id);
|
||||
|
||||
// Chance created in this transaction
|
||||
if (entity == null) {
|
||||
entity = (ClientSessionEntity) tx.get(sessionCache, id);
|
||||
}
|
||||
|
||||
if (entity != null) {
|
||||
RealmModel realm = session.realms().getRealm(entity.getRealm());
|
||||
return wrap(realm, entity);
|
||||
return wrap(realm, entity, false);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserSessionModel getUserSession(RealmModel realm, String id) {
|
||||
UserSessionEntity entity = (UserSessionEntity) sessionCache.get(id);
|
||||
return wrap(realm, entity);
|
||||
return getUserSession(realm, id, false);
|
||||
}
|
||||
|
||||
protected UserSessionAdapter getUserSession(RealmModel realm, String id, boolean offline) {
|
||||
Cache<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
|
||||
@@ -127,7 +165,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||
.reducedWith(new FirstResultReducer())
|
||||
.execute();
|
||||
|
||||
return wrapUserSessions(realm, sessions.values());
|
||||
return wrapUserSessions(realm, sessions.values(), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -137,7 +175,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||
.reducedWith(new FirstResultReducer())
|
||||
.execute();
|
||||
|
||||
return wrapUserSessions(realm, sessions.values());
|
||||
return wrapUserSessions(realm, sessions.values(), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -147,7 +185,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||
.reducedWith(new FirstResultReducer())
|
||||
.execute();
|
||||
|
||||
List<UserSessionModel> userSessionModels = wrapUserSessions(realm, sessions.values());
|
||||
List<UserSessionModel> userSessionModels = wrapUserSessions(realm, sessions.values(), false);
|
||||
if (userSessionModels.isEmpty()) return null;
|
||||
return userSessionModels.get(0);
|
||||
}
|
||||
@@ -159,7 +197,13 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||
|
||||
@Override
|
||||
public List<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())
|
||||
.reducedWith(new LargestResultReducer())
|
||||
.execute();
|
||||
@@ -192,9 +236,9 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||
|
||||
List<UserSessionModel> userSessions = new LinkedList<UserSessionModel>();
|
||||
for (Map.Entry<String, Integer> e : sessionTimestamps) {
|
||||
UserSessionEntity userSessionEntity = (UserSessionEntity) sessionCache.get(e.getKey());
|
||||
UserSessionEntity userSessionEntity = (UserSessionEntity) cache.get(e.getKey());
|
||||
if (userSessionEntity != null) {
|
||||
userSessions.add(wrap(realm, userSessionEntity));
|
||||
userSessions.add(wrap(realm, userSessionEntity, offline));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,13 +258,19 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||
.reducedWith(new FirstResultReducer())
|
||||
.execute();
|
||||
|
||||
return wrapUserSessions(realm, sessions.values());
|
||||
return wrapUserSessions(realm, sessions.values(), false);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getActiveUserSessions(RealmModel realm, ClientModel client) {
|
||||
Map map = new MapReduceTask(sessionCache)
|
||||
return getUserSessionsCount(realm, client, false);
|
||||
}
|
||||
|
||||
protected int getUserSessionsCount(RealmModel realm, ClientModel client, boolean offline) {
|
||||
Cache<String, SessionEntity> cache = getCache(offline);
|
||||
|
||||
Map map = new MapReduceTask(cache)
|
||||
.mappedWith(ClientSessionMapper.create(realm.getId()).client(client.getId()).emitUserSessionAndTimestamp())
|
||||
.reducedWith(new LargestResultReducer()).execute();
|
||||
|
||||
@@ -234,13 +284,19 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||
|
||||
@Override
|
||||
public void removeUserSessions(RealmModel realm, UserModel user) {
|
||||
Map<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())
|
||||
.reducedWith(new FirstResultReducer())
|
||||
.execute();
|
||||
|
||||
for (String id : sessions.keySet()) {
|
||||
removeUserSession(realm, id);
|
||||
removeUserSession(realm, id, offline);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,13 +327,19 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||
|
||||
@Override
|
||||
public void removeUserSessions(RealmModel realm) {
|
||||
Map<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())
|
||||
.reducedWith(new FirstResultReducer())
|
||||
.execute();
|
||||
|
||||
for (String id : ids.keySet()) {
|
||||
sessionCache.remove(id);
|
||||
cache.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,25 +381,39 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||
|
||||
@Override
|
||||
public void onRealmRemoved(RealmModel realm) {
|
||||
removeUserSessions(realm);
|
||||
removeUserSessions(realm, true);
|
||||
removeUserSessions(realm, false);
|
||||
removeAllUserLoginFailures(realm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClientRemoved(RealmModel realm, ClientModel client) {
|
||||
Map<String, String> map = new MapReduceTask(sessionCache)
|
||||
.mappedWith(ClientSessionMapper.create(realm.getId()).client(client.getId()).emitKey())
|
||||
onClientRemoved(realm, client, true);
|
||||
onClientRemoved(realm, client, false);
|
||||
}
|
||||
|
||||
private void onClientRemoved(RealmModel realm, ClientModel client, boolean offline) {
|
||||
Cache<String, SessionEntity> cache = getCache(offline);
|
||||
|
||||
Map<String, ClientSessionEntity> map = new MapReduceTask(cache)
|
||||
.mappedWith(ClientSessionMapper.create(realm.getId()).client(client.getId()))
|
||||
.reducedWith(new FirstResultReducer())
|
||||
.execute();
|
||||
|
||||
for (String id : map.keySet()) {
|
||||
tx.remove(sessionCache, id);
|
||||
for (Map.Entry<String, ClientSessionEntity> entry : map.entrySet()) {
|
||||
|
||||
// detach from userSession
|
||||
ClientSessionAdapter adapter = wrap(realm, entry.getValue(), offline);
|
||||
adapter.setUserSession(null);
|
||||
|
||||
tx.remove(cache, entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserRemoved(RealmModel realm, UserModel user) {
|
||||
removeUserSessions(realm, user);
|
||||
removeUserSessions(realm, user, true);
|
||||
removeUserSessions(realm, user, false);
|
||||
|
||||
loginFailureCache.remove(new LoginFailureKey(realm.getId(), user.getUsername()));
|
||||
loginFailureCache.remove(new LoginFailureKey(realm.getId(), user.getEmail()));
|
||||
@@ -347,20 +423,26 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||
public void close() {
|
||||
}
|
||||
|
||||
void attachSession(UserSessionModel userSession, ClientSessionModel clientSession) {
|
||||
UserSessionEntity entity = ((UserSessionAdapter) userSession).getEntity();
|
||||
void attachSession(UserSessionAdapter userSession, ClientSessionModel clientSession) {
|
||||
UserSessionEntity entity = userSession.getEntity();
|
||||
String clientSessionId = clientSession.getId();
|
||||
if (entity.getClientSessions() == null) {
|
||||
entity.setClientSessions(new HashSet<String>());
|
||||
}
|
||||
if (!entity.getClientSessions().contains(clientSessionId)) {
|
||||
entity.getClientSessions().add(clientSessionId);
|
||||
tx.replace(sessionCache, entity.getId(), entity);
|
||||
userSession.update();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeClientSession(RealmModel realm, ClientSessionModel clientSession) {
|
||||
removeClientSession(realm, clientSession, false);
|
||||
}
|
||||
|
||||
protected void removeClientSession(RealmModel realm, ClientSessionModel clientSession, boolean offline) {
|
||||
Cache<String, SessionEntity> cache = getCache(offline);
|
||||
|
||||
UserSessionModel userSession = clientSession.getUserSession();
|
||||
if (userSession != null) {
|
||||
UserSessionEntity entity = ((UserSessionAdapter) userSession).getEntity();
|
||||
@@ -368,34 +450,40 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||
entity.getClientSessions().remove(clientSession.getId());
|
||||
|
||||
}
|
||||
tx.replace(sessionCache, entity.getId(), entity);
|
||||
tx.replace(cache, entity.getId(), entity);
|
||||
}
|
||||
tx.remove(sessionCache, clientSession.getId());
|
||||
tx.remove(cache, clientSession.getId());
|
||||
}
|
||||
|
||||
|
||||
void dettachSession(UserSessionModel userSession, ClientSessionModel clientSession) {
|
||||
UserSessionEntity entity = ((UserSessionAdapter) userSession).getEntity();
|
||||
void dettachSession(UserSessionAdapter userSession, ClientSessionModel clientSession) {
|
||||
UserSessionEntity entity = userSession.getEntity();
|
||||
String clientSessionId = clientSession.getId();
|
||||
if (entity.getClientSessions() != null && entity.getClientSessions().contains(clientSessionId)) {
|
||||
entity.getClientSessions().remove(clientSessionId);
|
||||
if (entity.getClientSessions().isEmpty()) {
|
||||
entity.setClientSessions(null);
|
||||
}
|
||||
tx.replace(sessionCache, entity.getId(), entity);
|
||||
userSession.update();
|
||||
}
|
||||
}
|
||||
|
||||
protected void removeUserSession(RealmModel realm, String userSessionId) {
|
||||
tx.remove(sessionCache, userSessionId);
|
||||
removeUserSession(realm, userSessionId, false);
|
||||
}
|
||||
|
||||
Map<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())
|
||||
.reducedWith(new FirstResultReducer())
|
||||
.execute();
|
||||
|
||||
for (String id : map.keySet()) {
|
||||
tx.remove(sessionCache, id);
|
||||
tx.remove(cache, id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,20 +492,22 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||
return tx;
|
||||
}
|
||||
|
||||
UserSessionModel wrap(RealmModel realm, UserSessionEntity entity) {
|
||||
return entity != null ? new UserSessionAdapter(session, this, sessionCache, realm, entity) : null;
|
||||
UserSessionAdapter wrap(RealmModel realm, UserSessionEntity entity, boolean offline) {
|
||||
Cache<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>();
|
||||
for (UserSessionEntity e : entities) {
|
||||
models.add(wrap(realm, e));
|
||||
models.add(wrap(realm, e, offline));
|
||||
}
|
||||
return models;
|
||||
}
|
||||
|
||||
ClientSessionModel wrap(RealmModel realm, ClientSessionEntity entity) {
|
||||
return entity != null ? new ClientSessionAdapter(session, this, sessionCache, realm, entity) : null;
|
||||
ClientSessionAdapter wrap(RealmModel realm, ClientSessionEntity entity, boolean offline) {
|
||||
Cache<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;
|
||||
}
|
||||
|
||||
List<ClientSessionModel> wrapClientSessions(RealmModel realm, Collection<ClientSessionEntity> entities) {
|
||||
List<ClientSessionModel> wrapClientSessions(RealmModel realm, Collection<ClientSessionEntity> entities, boolean offline) {
|
||||
List<ClientSessionModel> models = new LinkedList<ClientSessionModel>();
|
||||
for (ClientSessionEntity e : entities) {
|
||||
models.add(wrap(realm, e));
|
||||
models.add(wrap(realm, e, offline));
|
||||
}
|
||||
return models;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public UserSessionModel createOfflineUserSession(UserSessionModel userSession) {
|
||||
UserSessionEntity entity = new UserSessionEntity();
|
||||
entity.setId(userSession.getId());
|
||||
entity.setRealm(userSession.getRealm().getId());
|
||||
|
||||
entity.setAuthMethod(userSession.getAuthMethod());
|
||||
entity.setBrokerSessionId(userSession.getBrokerSessionId());
|
||||
entity.setBrokerUserId(userSession.getBrokerUserId());
|
||||
entity.setIpAddress(userSession.getIpAddress());
|
||||
entity.setLastSessionRefresh(userSession.getLastSessionRefresh());
|
||||
entity.setLoginUsername(userSession.getLoginUsername());
|
||||
entity.setNotes(userSession.getNotes());
|
||||
entity.setRememberMe(userSession.isRememberMe());
|
||||
entity.setStarted(userSession.getStarted());
|
||||
entity.setState(userSession.getState());
|
||||
entity.setUser(userSession.getUser().getId());
|
||||
|
||||
tx.put(offlineSessionCache, userSession.getId(), entity);
|
||||
return wrap(userSession.getRealm(), entity, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserSessionModel getOfflineUserSession(RealmModel realm, String userSessionId) {
|
||||
return getUserSession(realm, userSessionId, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeOfflineUserSession(RealmModel realm, String userSessionId) {
|
||||
removeUserSession(realm, userSessionId, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSessionModel createOfflineClientSession(ClientSessionModel clientSession) {
|
||||
ClientSessionEntity entity = new ClientSessionEntity();
|
||||
entity.setId(clientSession.getId());
|
||||
entity.setRealm(clientSession.getRealm().getId());
|
||||
|
||||
entity.setAction(clientSession.getAction());
|
||||
entity.setAuthenticatorStatus(clientSession.getExecutionStatus());
|
||||
entity.setAuthMethod(clientSession.getAuthMethod());
|
||||
if (clientSession.getAuthenticatedUser() != null) {
|
||||
entity.setAuthUserId(clientSession.getAuthenticatedUser().getId());
|
||||
}
|
||||
entity.setClient(clientSession.getClient().getId());
|
||||
entity.setNotes(clientSession.getNotes());
|
||||
entity.setProtocolMappers(clientSession.getProtocolMappers());
|
||||
entity.setRedirectUri(clientSession.getRedirectUri());
|
||||
entity.setRoles(clientSession.getRoles());
|
||||
entity.setTimestamp(clientSession.getTimestamp());
|
||||
entity.setUserSessionNotes(clientSession.getUserSessionNotes());
|
||||
|
||||
tx.put(offlineSessionCache, clientSession.getId(), entity);
|
||||
return wrap(clientSession.getRealm(), entity, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSessionModel getOfflineClientSession(RealmModel realm, String clientSessionId) {
|
||||
return getClientSession(realm, clientSessionId, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<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 {
|
||||
|
||||
private boolean active;
|
||||
@@ -478,17 +667,19 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||
public void put(Cache cache, Object key, Object value) {
|
||||
log.tracev("Adding cache operation: {0} on {1}", CacheOperation.ADD, key);
|
||||
|
||||
if (tasks.containsKey(key)) {
|
||||
Object taskKey = getTaskKey(cache, key);
|
||||
if (tasks.containsKey(taskKey)) {
|
||||
throw new IllegalStateException("Can't add session: task in progress for session");
|
||||
} else {
|
||||
tasks.put(key, new CacheTask(cache, CacheOperation.ADD, key, value));
|
||||
tasks.put(taskKey, new CacheTask(cache, CacheOperation.ADD, key, value));
|
||||
}
|
||||
}
|
||||
|
||||
public void replace(Cache cache, Object key, Object value) {
|
||||
log.tracev("Adding cache operation: {0} on {1}", CacheOperation.REPLACE, key);
|
||||
|
||||
CacheTask current = tasks.get(key);
|
||||
Object taskKey = getTaskKey(cache, key);
|
||||
CacheTask current = tasks.get(taskKey);
|
||||
if (current != null) {
|
||||
switch (current.operation) {
|
||||
case ADD:
|
||||
@@ -499,14 +690,40 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
tasks.put(key, new CacheTask(cache, CacheOperation.REPLACE, key, value));
|
||||
tasks.put(taskKey, new CacheTask(cache, CacheOperation.REPLACE, key, value));
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(Cache cache, Object key) {
|
||||
log.tracev("Adding cache operation: {0} on {1}", CacheOperation.REMOVE, key);
|
||||
|
||||
tasks.put(key, new CacheTask(cache, CacheOperation.REMOVE, key, null));
|
||||
Object taskKey = getTaskKey(cache, key);
|
||||
tasks.put(taskKey, new CacheTask(cache, CacheOperation.REMOVE, key, null));
|
||||
}
|
||||
|
||||
// This is for possibility to lookup for session by id, which was created in this transaction
|
||||
public Object get(Cache cache, Object key) {
|
||||
Object taskKey = getTaskKey(cache, key);
|
||||
CacheTask current = tasks.get(taskKey);
|
||||
if (current != null) {
|
||||
switch (current.operation) {
|
||||
case ADD:
|
||||
case REPLACE:
|
||||
return current.value; }
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Object getTaskKey(Cache cache, Object key) {
|
||||
if (key instanceof String) {
|
||||
return new StringBuilder(cache.getName())
|
||||
.append("::")
|
||||
.append(key.toString()).toString();
|
||||
} else {
|
||||
// loginFailure cache
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
public class CacheTask {
|
||||
|
||||
+74
-14
@@ -7,12 +7,18 @@ import org.keycloak.Config;
|
||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.KeycloakSessionTask;
|
||||
import org.keycloak.models.UserSessionProvider;
|
||||
import org.keycloak.models.UserSessionProviderFactory;
|
||||
import org.keycloak.models.session.UserSessionPersisterProvider;
|
||||
import org.keycloak.models.sessions.infinispan.compat.MemUserSessionProviderFactory;
|
||||
import org.keycloak.models.sessions.infinispan.compat.SimpleUserSessionInitializer;
|
||||
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
|
||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.initializer.InfinispanUserSessionInitializer;
|
||||
import org.keycloak.models.sessions.infinispan.initializer.OfflineUserSessionLoader;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
||||
/**
|
||||
* Uses Infinispan to store user sessions. On EAP 6.4 (Infinispan 5.2) map reduce is not supported for local caches as a work around
|
||||
@@ -24,28 +30,19 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
||||
|
||||
private static final Logger log = Logger.getLogger(InfinispanUserSessionProviderFactory.class);
|
||||
|
||||
private Config.Scope config;
|
||||
private Boolean compatMode;
|
||||
private MemUserSessionProviderFactory compatProviderFactory;
|
||||
|
||||
@Override
|
||||
public UserSessionProvider create(KeycloakSession session) {
|
||||
if (compatMode == null) {
|
||||
synchronized (this) {
|
||||
if (compatMode == null) {
|
||||
compatMode = isCompatMode(session);
|
||||
if (compatMode) {
|
||||
compatProviderFactory = new MemUserSessionProviderFactory();
|
||||
log.info("Infinispan version doesn't support map reduce for local cache. Falling back to deprecated mem user session provider.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!compatMode) {
|
||||
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
|
||||
Cache<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);
|
||||
return new InfinispanUserSessionProvider(session, cache, loginFailures);
|
||||
return new InfinispanUserSessionProvider(session, cache, offlineSessionsCache, loginFailures);
|
||||
} else {
|
||||
return compatProviderFactory.create(session);
|
||||
}
|
||||
@@ -53,11 +50,67 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
public void postInit(final KeycloakSessionFactory factory) {
|
||||
KeycloakModelUtils.runJobInTransaction(factory, new KeycloakSessionTask() {
|
||||
|
||||
@Override
|
||||
public void run(KeycloakSession session) {
|
||||
compatMode = isCompatMode(session);
|
||||
if (compatMode) {
|
||||
compatProviderFactory = new MemUserSessionProviderFactory();
|
||||
}
|
||||
|
||||
log.debug("Clearing detached sessions from persistent storage");
|
||||
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||
if (persister == null) {
|
||||
throw new RuntimeException("userSessionPersister not configured. Please see the migration docs and upgrade your configuration");
|
||||
} else {
|
||||
persister.clearDetachedUserSessions();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Max count of worker errors. Initialization will end with exception when this number is reached
|
||||
int maxErrors = config.getInt("maxErrors", 50);
|
||||
|
||||
// Count of sessions to be computed in each segment
|
||||
int sessionsPerSegment = config.getInt("sessionsPerSegment", 100);
|
||||
|
||||
// TODO: Possibility to run this asynchronously to not block start time
|
||||
loadPersistentSessions(factory, maxErrors, sessionsPerSegment);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void loadPersistentSessions(final KeycloakSessionFactory sessionFactory, final int maxErrors, final int sessionsPerSegment) {
|
||||
log.debug("Start pre-loading userSessions and clientSessions from persistent storage");
|
||||
|
||||
if (compatMode) {
|
||||
SimpleUserSessionInitializer initializer = new SimpleUserSessionInitializer(sessionFactory, new OfflineUserSessionLoader(), sessionsPerSegment);
|
||||
initializer.loadPersistentSessions();
|
||||
|
||||
} else {
|
||||
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||
|
||||
@Override
|
||||
public void run(KeycloakSession session) {
|
||||
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
|
||||
Cache<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
|
||||
@@ -72,11 +125,18 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
||||
return "infinispan";
|
||||
}
|
||||
|
||||
private static boolean isCompatMode(KeycloakSession session) {
|
||||
private boolean isCompatMode(KeycloakSession session) {
|
||||
// For unit tests
|
||||
if (this.config.getBoolean("enforceCompat", false)) {
|
||||
log.info("Enforced compatibility mode for infinispan. Falling back to deprecated mem user session provider.");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Version.getVersionShort() < Version.getVersionShort("5.3.0.Final")) {
|
||||
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
|
||||
Cache<String, SessionEntity> cache = connections.getCache(InfinispanConnectionProvider.SESSION_CACHE_NAME);
|
||||
if (cache.getAdvancedCache().getRpcManager() == null) {
|
||||
log.info("Infinispan version doesn't support map reduce for local cache. Falling back to deprecated mem user session provider.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+13
-4
@@ -31,18 +31,27 @@ public class UserSessionAdapter implements UserSessionModel {
|
||||
|
||||
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.provider = provider;
|
||||
this.cache = cache;
|
||||
this.realm = realm;
|
||||
this.entity = entity;
|
||||
this.offline = offline;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return entity.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel getRealm() {
|
||||
return realm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBrokerSessionId() {
|
||||
return entity.getBrokerSessionId();
|
||||
@@ -129,14 +138,14 @@ public class UserSessionAdapter implements UserSessionModel {
|
||||
@Override
|
||||
public List<ClientSessionModel> getClientSessions() {
|
||||
if (entity.getClientSessions() != null) {
|
||||
List<ClientSessionEntity> clientSessions = new LinkedList<ClientSessionEntity>();
|
||||
List<ClientSessionModel> clientSessions = new LinkedList<>();
|
||||
for (String c : entity.getClientSessions()) {
|
||||
ClientSessionEntity clientSession = (ClientSessionEntity) cache.get(c);
|
||||
ClientSessionModel clientSession = provider.getClientSession(realm, c, offline);
|
||||
if (clientSession != null) {
|
||||
clientSessions.add(clientSession);
|
||||
}
|
||||
}
|
||||
return provider.wrapClientSessions(realm, clientSessions);
|
||||
return clientSessions;
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
+189
-30
@@ -4,6 +4,7 @@ import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
@@ -38,13 +39,21 @@ public class MemUserSessionProvider implements UserSessionProvider {
|
||||
private final ConcurrentHashMap<String, ClientSessionEntity> clientSessions;
|
||||
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.userSessions = userSessions;
|
||||
this.clientSessions = clientSessions;
|
||||
this.loginFailures = loginFailures;
|
||||
this.userSessionsByBrokerSessionId = userSessionsByBrokerSessionId;
|
||||
this.userSessionsByBrokerUserId = userSessionsByBrokerUserId;
|
||||
this.offlineUserSessions = offlineUserSessions;
|
||||
this.offlineClientSessions = offlineClientSessions;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -189,6 +198,12 @@ public class MemUserSessionProvider implements UserSessionProvider {
|
||||
|
||||
@Override
|
||||
public List<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>();
|
||||
for (ClientSessionEntity s : clientSessions.values()) {
|
||||
String realmId = realm.getId();
|
||||
@@ -210,7 +225,11 @@ public class MemUserSessionProvider implements UserSessionProvider {
|
||||
|
||||
@Override
|
||||
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()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
@@ -221,7 +240,7 @@ public class MemUserSessionProvider implements UserSessionProvider {
|
||||
|
||||
@Override
|
||||
public int getActiveUserSessions(RealmModel realm, ClientModel client) {
|
||||
return getUserSessions(realm, client).size();
|
||||
return getUserSessions(realm, client, false).size();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -229,40 +248,51 @@ public class MemUserSessionProvider implements UserSessionProvider {
|
||||
UserSessionEntity entity = getUserSessionEntity(realm, session.getId());
|
||||
if (entity != null) {
|
||||
userSessions.remove(entity.getId());
|
||||
remove(entity);
|
||||
remove(entity, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUserSessions(RealmModel realm, UserModel user) {
|
||||
Iterator<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()) {
|
||||
UserSessionEntity s = itr.next();
|
||||
if (s.getRealm().equals(realm.getId()) && s.getUser().equals(user.getId())) {
|
||||
itr.remove();
|
||||
remove(s);
|
||||
remove(s, offline);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void remove(UserSessionEntity s) {
|
||||
if (s.getBrokerSessionId() != null) {
|
||||
userSessionsByBrokerSessionId.remove(s.getBrokerSessionId());
|
||||
}
|
||||
if (s.getBrokerUserId() != null) {
|
||||
Set<String> set = userSessionsByBrokerUserId.get(s.getBrokerUserId());
|
||||
if (set != null) {
|
||||
synchronized (set) {
|
||||
set.remove(s.getId());
|
||||
// this is a race condition :(
|
||||
// Since it will be very rare for a user to have concurrent sessions, I'm hoping we never hit this
|
||||
if (set.isEmpty()) userSessionsByBrokerUserId.remove(s.getBrokerUserId());
|
||||
protected void remove(UserSessionEntity s, boolean offline) {
|
||||
if (offline) {
|
||||
for (ClientSessionEntity clientSession : s.getClientSessions()) {
|
||||
offlineClientSessions.remove(clientSession.getId());
|
||||
}
|
||||
} else {
|
||||
if (s.getBrokerSessionId() != null) {
|
||||
userSessionsByBrokerSessionId.remove(s.getBrokerSessionId());
|
||||
}
|
||||
if (s.getBrokerUserId() != null) {
|
||||
Set<String> set = userSessionsByBrokerUserId.get(s.getBrokerUserId());
|
||||
if (set != null) {
|
||||
synchronized (set) {
|
||||
set.remove(s.getId());
|
||||
// this is a race condition :(
|
||||
// Since it will be very rare for a user to have concurrent sessions, I'm hoping we never hit this
|
||||
if (set.isEmpty()) userSessionsByBrokerUserId.remove(s.getBrokerUserId());
|
||||
}
|
||||
}
|
||||
}
|
||||
for (ClientSessionEntity clientSession : s.getClientSessions()) {
|
||||
clientSessions.remove(clientSession.getId());
|
||||
}
|
||||
}
|
||||
for (ClientSessionEntity clientSession : s.getClientSessions()) {
|
||||
clientSessions.remove(clientSession.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -273,7 +303,7 @@ public class MemUserSessionProvider implements UserSessionProvider {
|
||||
if (s.getRealm().equals(realm.getId()) && (s.getLastSessionRefresh() < Time.currentTime() - realm.getSsoSessionIdleTimeout() || s.getStarted() < Time.currentTime() - realm.getSsoSessionMaxLifespan())) {
|
||||
itr.remove();
|
||||
|
||||
remove(s);
|
||||
remove(s, false);
|
||||
}
|
||||
}
|
||||
int expired = Time.currentTime() - RealmInfoUtil.getDettachedClientSessionLifespan(realm);
|
||||
@@ -288,16 +318,19 @@ public class MemUserSessionProvider implements UserSessionProvider {
|
||||
|
||||
@Override
|
||||
public void removeUserSessions(RealmModel realm) {
|
||||
Iterator<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()) {
|
||||
UserSessionEntity s = itr.next();
|
||||
if (s.getRealm().equals(realm.getId())) {
|
||||
itr.remove();
|
||||
|
||||
remove(s);
|
||||
remove(s, offline);
|
||||
}
|
||||
}
|
||||
Iterator<ClientSessionEntity> citr = clientSessions.values().iterator();
|
||||
Iterator<ClientSessionEntity> citr = offline ? offlineClientSessions.values().iterator() : clientSessions.values().iterator();
|
||||
while (citr.hasNext()) {
|
||||
ClientSessionEntity c = citr.next();
|
||||
if (c.getSession() == null && c.getRealmId().equals(realm.getId())) {
|
||||
@@ -340,15 +373,23 @@ public class MemUserSessionProvider implements UserSessionProvider {
|
||||
|
||||
@Override
|
||||
public void onRealmRemoved(RealmModel realm) {
|
||||
removeUserSessions(realm);
|
||||
removeUserSessions(realm, true);
|
||||
removeUserSessions(realm, false);
|
||||
removeAllUserLoginFailures(realm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClientRemoved(RealmModel realm, ClientModel client) {
|
||||
for (ClientSessionEntity e : clientSessions.values()) {
|
||||
onClientRemoved(realm, client, true);
|
||||
onClientRemoved(realm, client, false);
|
||||
}
|
||||
|
||||
private void onClientRemoved(RealmModel realm, ClientModel client, boolean offline) {
|
||||
ConcurrentHashMap<String, ClientSessionEntity> clientSessionsMap = offline ? offlineClientSessions : clientSessions;
|
||||
|
||||
for (ClientSessionEntity e : clientSessionsMap.values()) {
|
||||
if (e.getRealmId().equals(realm.getId()) && e.getClientId().equals(client.getId())) {
|
||||
clientSessions.remove(e.getId());
|
||||
clientSessionsMap.remove(e.getId());
|
||||
e.getSession().removeClientSession(e);
|
||||
}
|
||||
}
|
||||
@@ -356,12 +397,130 @@ public class MemUserSessionProvider implements UserSessionProvider {
|
||||
|
||||
@Override
|
||||
public void onUserRemoved(RealmModel realm, UserModel user) {
|
||||
removeUserSessions(realm, user);
|
||||
removeUserSessions(realm, user, true);
|
||||
removeUserSessions(realm, user, false);
|
||||
|
||||
loginFailures.remove(new UsernameLoginFailureKey(realm.getId(), user.getUsername()));
|
||||
loginFailures.remove(new UsernameLoginFailureKey(realm.getId(), user.getEmail()));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public UserSessionModel createOfflineUserSession(UserSessionModel userSession) {
|
||||
UserSessionEntity entity = new UserSessionEntity();
|
||||
entity.setId(userSession.getId());
|
||||
entity.setRealm(userSession.getRealm().getId());
|
||||
|
||||
entity.setAuthMethod(userSession.getAuthMethod());
|
||||
entity.setBrokerSessionId(userSession.getBrokerSessionId());
|
||||
entity.setBrokerUserId(userSession.getBrokerUserId());
|
||||
entity.setIpAddress(userSession.getIpAddress());
|
||||
entity.setLastSessionRefresh(userSession.getLastSessionRefresh());
|
||||
entity.setLoginUsername(userSession.getLoginUsername());
|
||||
if (userSession.getNotes() != null) {
|
||||
entity.getNotes().putAll(userSession.getNotes());
|
||||
}
|
||||
entity.setRememberMe(userSession.isRememberMe());
|
||||
entity.setStarted(userSession.getStarted());
|
||||
entity.setState(userSession.getState());
|
||||
entity.setUser(userSession.getUser().getId());
|
||||
|
||||
offlineUserSessions.put(userSession.getId(), entity);
|
||||
return new UserSessionAdapter(session, this, userSession.getRealm(), entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserSessionModel getOfflineUserSession(RealmModel realm, String userSessionId) {
|
||||
UserSessionEntity entity = offlineUserSessions.get(userSessionId);
|
||||
if (entity != null && entity.getRealm().equals(realm.getId())) {
|
||||
return new UserSessionAdapter(session, this, realm, entity);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeOfflineUserSession(RealmModel realm, String userSessionId) {
|
||||
UserSessionEntity entity = offlineUserSessions.get(userSessionId);
|
||||
if (entity != null && entity.getRealm().equals(realm.getId())) {
|
||||
offlineUserSessions.remove(entity);
|
||||
remove(entity, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSessionModel createOfflineClientSession(ClientSessionModel clientSession) {
|
||||
ClientSessionEntity entity = new ClientSessionEntity();
|
||||
entity.setId(clientSession.getId());
|
||||
entity.setRealmId(clientSession.getRealm().getId());
|
||||
|
||||
entity.setAction(clientSession.getAction());
|
||||
entity.setAuthenticatorStatus(clientSession.getExecutionStatus());
|
||||
entity.setAuthMethod(clientSession.getAuthMethod());
|
||||
if (clientSession.getAuthenticatedUser() != null) {
|
||||
entity.setAuthUserId(clientSession.getAuthenticatedUser().getId());
|
||||
}
|
||||
entity.setClientId(clientSession.getClient().getId());
|
||||
if (clientSession.getNotes() != null) {
|
||||
entity.getNotes().putAll(clientSession.getNotes());
|
||||
}
|
||||
entity.setProtocolMappers(clientSession.getProtocolMappers());
|
||||
entity.setRedirectUri(clientSession.getRedirectUri());
|
||||
entity.setRoles(clientSession.getRoles());
|
||||
entity.setTimestamp(clientSession.getTimestamp());
|
||||
|
||||
if (clientSession.getUserSessionNotes() != null) {
|
||||
entity.getUserSessionNotes().putAll(clientSession.getUserSessionNotes());
|
||||
}
|
||||
|
||||
offlineClientSessions.put(clientSession.getId(), entity);
|
||||
return new ClientSessionAdapter(session, this, clientSession.getRealm(), entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSessionModel getOfflineClientSession(RealmModel realm, String clientSessionId) {
|
||||
ClientSessionEntity entity = offlineClientSessions.get(clientSessionId);
|
||||
if (entity != null && entity.getRealmId().equals(realm.getId())) {
|
||||
return new ClientSessionAdapter(session, this, realm, entity);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<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
|
||||
public void close() {
|
||||
}
|
||||
|
||||
+5
-1
@@ -26,8 +26,12 @@ public class MemUserSessionProviderFactory {
|
||||
private final ConcurrentHashMap<String, String> userSessionsByBrokerSessionId = 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) {
|
||||
return new MemUserSessionProvider(session, userSessions, userSessionsByBrokerSessionId, userSessionsByBrokerUserId, clientSessions, loginFailures);
|
||||
return new MemUserSessionProvider(session, userSessions, userSessionsByBrokerSessionId, userSessionsByBrokerUserId, clientSessions, loginFailures,
|
||||
offlineUserSessions, offlineClientSessions);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel getRealm() {
|
||||
return realm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBrokerSessionId() {
|
||||
return entity.getBrokerSessionId();
|
||||
|
||||
-10
@@ -22,8 +22,6 @@ public class ClientSessionEntity extends SessionEntity {
|
||||
|
||||
private String redirectUri;
|
||||
|
||||
private String state;
|
||||
|
||||
private int timestamp;
|
||||
|
||||
private String action;
|
||||
@@ -69,14 +67,6 @@ public class ClientSessionEntity extends SessionEntity {
|
||||
this.redirectUri = redirectUri;
|
||||
}
|
||||
|
||||
public String getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(String state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public int getTimestamp() {
|
||||
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.managers.AuthenticationManager;
|
||||
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.Time;
|
||||
|
||||
@@ -98,7 +98,7 @@ public class TokenManager {
|
||||
ClientSessionModel clientSession = null;
|
||||
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) {
|
||||
userSession = clientSession.getUserSession();
|
||||
}
|
||||
@@ -490,14 +490,15 @@ public class TokenManager {
|
||||
String scopeParam = clientSession.getNote(OIDCLoginProtocol.SCOPE_PARAM);
|
||||
boolean offlineTokenRequested = TokenUtil.isOfflineTokenRequested(scopeParam);
|
||||
if (offlineTokenRequested) {
|
||||
if (!OfflineTokenUtils.isOfflineTokenAllowed(realm, clientSession)) {
|
||||
UserSessionManager sessionManager = new UserSessionManager(session);
|
||||
if (!sessionManager.isOfflineTokenAllowed(clientSession)) {
|
||||
event.error(Errors.NOT_ALLOWED);
|
||||
throw new ErrorResponseException("not_allowed", "Offline tokens not allowed for the user or client", Response.Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
refreshToken = new RefreshToken(accessToken);
|
||||
refreshToken.type(TokenUtil.TOKEN_TYPE_OFFLINE);
|
||||
OfflineTokenUtils.persistOfflineSession(session, realm, clientSession, userSession);
|
||||
sessionManager.persistOfflineSession(clientSession, userSession);
|
||||
} else {
|
||||
refreshToken = new RefreshToken(accessToken);
|
||||
refreshToken.expiration(Time.currentTime() + realm.getSsoSessionIdleTimeout());
|
||||
|
||||
@@ -2,16 +2,15 @@ package org.keycloak.services;
|
||||
|
||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||
import org.keycloak.ClientConnection;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakContext;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.*;
|
||||
import org.keycloak.models.utils.RealmImporter;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.services.resources.KeycloakApplication;
|
||||
import org.keycloak.services.util.LocaleHelper;
|
||||
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
@@ -87,4 +86,9 @@ public class DefaultKeycloakContext implements KeycloakContext {
|
||||
manager.setContextPath(getContextPath());
|
||||
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.jose.jws.JWSBuilder;
|
||||
import org.keycloak.login.LoginFormsProvider;
|
||||
import org.keycloak.models.ClientModel;
|
||||
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.*;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.protocol.LoginProtocol;
|
||||
import org.keycloak.protocol.RestartLoginCookie;
|
||||
import org.keycloak.protocol.oidc.TokenManager;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.services.Urls;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.services.resources.IdentityBrokerService;
|
||||
import org.keycloak.services.resources.RealmsResource;
|
||||
import org.keycloak.services.Urls;
|
||||
import org.keycloak.services.util.CookieHelper;
|
||||
import org.keycloak.util.Time;
|
||||
|
||||
import javax.ws.rs.core.Cookie;
|
||||
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 javax.ws.rs.core.*;
|
||||
import java.net.URI;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import org.keycloak.freemarker.LocaleHelper;
|
||||
import org.keycloak.util.TokenUtil;
|
||||
|
||||
/**
|
||||
* 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!
|
||||
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,
|
||||
ClientConnection clientConnection,
|
||||
HttpRequest request, UriInfo uriInfo, EventBuilder event) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionProvider;
|
||||
import org.keycloak.models.session.UserSessionPersisterProvider;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper;
|
||||
@@ -54,6 +55,11 @@ public class ClientManager {
|
||||
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);
|
||||
if (serviceAccountUser != null) {
|
||||
realmManager.getSession().users().removeUser(realm, serviceAccountUser);
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.keycloak.services.managers;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.enums.SslRequired;
|
||||
import org.keycloak.models.session.UserSessionPersisterProvider;
|
||||
import org.keycloak.models.utils.RealmImporter;
|
||||
import org.keycloak.models.AccountRoles;
|
||||
import org.keycloak.models.AdminRoles;
|
||||
@@ -197,6 +198,11 @@ public class RealmManager implements RealmImporter {
|
||||
sessions.onRealmRemoved(realm);
|
||||
}
|
||||
|
||||
UserSessionPersisterProvider sessionsPersister = session.getProvider(UserSessionPersisterProvider.class);
|
||||
if (sessionsPersister != null) {
|
||||
sessionsPersister.onRealmRemoved(realm);
|
||||
}
|
||||
|
||||
// Remove all periodic syncs for configured federation providers
|
||||
UsersSyncManager usersSyncManager = new UsersSyncManager();
|
||||
for (final UserFederationProviderModel fedProvider : federationProviders) {
|
||||
@@ -210,10 +216,10 @@ public class RealmManager implements RealmImporter {
|
||||
realm.setEventsEnabled(rep.isEventsEnabled());
|
||||
realm.setEventsExpiration(rep.getEventsExpiration() != null ? rep.getEventsExpiration() : 0);
|
||||
if (rep.getEventsListeners() != null) {
|
||||
realm.setEventsListeners(new HashSet<String>(rep.getEventsListeners()));
|
||||
realm.setEventsListeners(new HashSet<>(rep.getEventsListeners()));
|
||||
}
|
||||
if(rep.getEnabledEventTypes() != null) {
|
||||
realm.setEnabledEventTypes(new HashSet<String>(rep.getEnabledEventTypes()));
|
||||
realm.setEnabledEventTypes(new HashSet<>(rep.getEnabledEventTypes()));
|
||||
}
|
||||
|
||||
realm.setAdminEventsEnabled(rep.isAdminEventsEnabled());
|
||||
|
||||
@@ -4,6 +4,7 @@ import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionProvider;
|
||||
import org.keycloak.models.session.UserSessionPersisterProvider;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
@@ -21,6 +22,12 @@ public class UserManager {
|
||||
if (sessions != null) {
|
||||
sessions.onUserRemoved(realm, user);
|
||||
}
|
||||
|
||||
UserSessionPersisterProvider sessionsPersister = session.getProvider(UserSessionPersisterProvider.class);
|
||||
if (sessionsPersister != null) {
|
||||
sessionsPersister.onUserRemoved(realm, user);
|
||||
}
|
||||
|
||||
if (session.users().removeUser(realm, user)) {
|
||||
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