Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Bill Burke committed 2015-10-14 10:51:14 -04:00
commit aadd63e2b0
122 files changed
+4818 -2265

No files matched your search

+1
View File
@@ -27,5 +27,6 @@
<artifactId>infinispan-core</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
@@ -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);
}
}
}
}
}
@@ -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>
@@ -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";
}
@@ -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>&lt;WILDFLY_HOME&gt;/bin/jboss-cli.sh -c --file=keycloak-prepare.cli</programlisting>
Or if you are running in clustering (HA) mode run:
<programlisting>&lt;WILDFLY_HOME&gt;/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>&lt;WILDFLY_HOME&gt;/bin/jboss-cli.sh -c :reload</programlisting>
Finally you need to run:
<programlisting>&lt;WILDFLY_HOME&gt;/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>&lt;EAP_HOME&gt;/bin/jboss-cli.sh -c --file=keycloak-prepare.cli</programlisting>
</listitem>
<listitem>Restart the server with the same server-config.</listitem>
<listitem>
<programlisting>&lt;EAP_HOME&gt;/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>
@@ -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;
}
@@ -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);
@@ -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(" ");
};
});
@@ -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">
+5
View File
@@ -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>
@@ -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));
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
}
@@ -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;
@@ -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;
}
}
}
@@ -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);
}
@@ -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;
@@ -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.
*
@@ -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);
}
}
@@ -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;
@@ -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;
}
}
@@ -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;
@@ -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() {
}
}
@@ -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;
}
}
@@ -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;
}
}
}
@@ -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;
}
}
}
@@ -0,0 +1 @@
org.keycloak.models.jpa.session.JpaUserSessionPersisterProviderFactory
@@ -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;
}
}
@@ -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() {
}
}
@@ -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;
@@ -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 {
}
@@ -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 {
}
@@ -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) {
}
}
@@ -0,0 +1 @@
org.keycloak.models.mongo.keycloak.adapters.MongoUserSessionPersisterProviderFactory
+5
View File
@@ -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>
@@ -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
@@ -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 {
@@ -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;
}
}
@@ -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();
}
@@ -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() {
}
@@ -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() {
@@ -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);
}
}
});
}
}
@@ -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();
@@ -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;
}
@@ -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;
}
}
}
@@ -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();
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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);
}
@@ -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);
}
}
}
}
@@ -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);
}
}
@@ -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