Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Vlasta Ramik 2015-10-15 12:30:17 +02:00
commit aaddaa6c25
200 changed files with 5909 additions and 2962 deletions

View file

@ -27,5 +27,6 @@
<artifactId>infinispan-core</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View file

@ -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);
}
}
}
}
}

View file

@ -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);

View file

@ -10,22 +10,27 @@
<addColumn tableName="CLIENT">
<column name="ROOT_URL" type="VARCHAR(255)"/>
<column name="DESCRIPTION" type="VARCHAR(255)"/>
</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 +40,19 @@
<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"/>
<addColumn tableName="REALM">
<column name="REVOKE_REFRESH_TOKEN" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
</addColumn>
</changeSet>
</databaseChangeLog>

View file

@ -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>

View file

@ -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() {

View file

@ -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";
}

View file

@ -11,6 +11,7 @@ public class ClientRepresentation {
protected String id;
protected String clientId;
protected String name;
protected String description;
protected String rootUrl;
protected String adminUrl;
protected String baseUrl;
@ -51,6 +52,14 @@ public class ClientRepresentation {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getClientId() {
return clientId;
}

View file

@ -10,6 +10,7 @@ public class RealmRepresentation {
protected String id;
protected String realm;
protected Integer notBefore;
protected Boolean revokeRefreshToken;
protected Integer accessTokenLifespan;
protected Integer ssoSessionIdleTimeout;
protected Integer ssoSessionMaxLifespan;
@ -166,6 +167,14 @@ public class RealmRepresentation {
this.sslRequired = sslRequired;
}
public Boolean getRevokeRefreshToken() {
return revokeRefreshToken;
}
public void setRevokeRefreshToken(Boolean revokeRefreshToken) {
this.revokeRefreshToken = revokeRefreshToken;
}
public Integer getAccessTokenLifespan() {
return accessTokenLifespan;
}

View file

@ -141,22 +141,6 @@
<artifactId>mongo-java-driver</artifactId>
</dependency>
<!-- export/import -->
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-export-import-zip</artifactId>
</dependency>
<dependency>
<groupId>de.idyl</groupId>
<artifactId>winzipaes</artifactId>
<exclusions>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>

View file

@ -22,6 +22,10 @@
"provider": "jpa"
},
"userSessionPersister": {
"provider": "jpa"
},
"timer": {
"provider": "basic"
},

View file

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.3" name="de.idyl.winzipaes">
<resources>
<artifact name="${de.idyl:winzipaes}"/>
</resources>
<dependencies>
<module name="javax.api"/>
<module name="org.bouncycastle"/>
</dependencies>
</module>

View file

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.3" name="org.keycloak.keycloak-export-import-zip">
<resources>
<artifact name="${org.keycloak:keycloak-export-import-zip}"/>
</resources>
<dependencies>
<module name="org.keycloak.keycloak-common"/>
<module name="org.keycloak.keycloak-core"/>
<module name="org.keycloak.keycloak-model-api"/>
<module name="org.keycloak.keycloak-invalidation-cache-model"/>
<module name="org.keycloak.keycloak-export-import-api"/>
<module name="javax.ws.rs.api"/>
<module name="org.codehaus.jackson.jackson-core-asl"/>
<module name="org.codehaus.jackson.jackson-mapper-asl"/>
<module name="org.codehaus.jackson.jackson-xc"/>
<module name="org.jboss.resteasy.resteasy-jaxrs"/>
<module name="org.jboss.logging"/>
<module name="org.bouncycastle" />
<module name="de.idyl.winzipaes"/>
<module name="javax.api"/>
</dependencies>
</module>

View file

@ -21,7 +21,6 @@
<module name="org.keycloak.keycloak-export-import-api" services="import"/>
<module name="org.keycloak.keycloak-export-import-dir" services="import"/>
<module name="org.keycloak.keycloak-export-import-single-file" services="import"/>
<module name="org.keycloak.keycloak-export-import-zip" services="import"/>
<module name="org.keycloak.keycloak-forms-common-freemarker" services="import"/>
<module name="org.keycloak.keycloak-forms-common-themes" services="import"/>
<module name="org.keycloak.keycloak-invalidation-cache-infinispan" services="import"/>

View file

@ -31,7 +31,6 @@
<module name="org.keycloak.keycloak-export-import-api" services="import"/>
<module name="org.keycloak.keycloak-export-import-dir" services="import"/>
<module name="org.keycloak.keycloak-export-import-single-file" services="import"/>
<module name="org.keycloak.keycloak-export-import-zip" services="import"/>
<module name="org.keycloak.keycloak-forms-common-freemarker" services="import"/>
<module name="org.keycloak.keycloak-forms-common-themes" services="import"/>
<module name="org.keycloak.keycloak-invalidation-cache-infinispan" services="import"/>

View file

@ -278,15 +278,6 @@
<maven-resource group="org.mongodb" artifact="mongo-java-driver"/>
</module-def>
<!-- export/import -->
<module-def name="org.keycloak.keycloak-export-import-zip">
<maven-resource group="org.keycloak" artifact="keycloak-export-import-zip"/>
</module-def>
<module-def name="de.idyl.winzipaes">
<maven-resource group="de.idyl" artifact="winzipaes"/>
</module-def>
<module-def name="org.liquibase">
<maven-resource group="org.liquibase" artifact="liquibase-core"/>
</module-def>

View file

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.1" name="de.idyl.winzipaes">
<resources>
<!-- Insert resources here -->
</resources>
<dependencies>
<module name="javax.api"/>
</dependencies>
</module>

View file

@ -21,7 +21,6 @@
<module name="org.keycloak.keycloak-export-import-api" services="import"/>
<module name="org.keycloak.keycloak-export-import-dir" services="import"/>
<module name="org.keycloak.keycloak-export-import-single-file" services="import"/>
<module name="org.keycloak.keycloak-export-import-zip" services="import"/>
<module name="org.keycloak.keycloak-forms-common-freemarker" services="import"/>
<module name="org.keycloak.keycloak-forms-common-themes" services="import"/>
<module name="org.keycloak.keycloak-invalidation-cache-infinispan" services="import"/>

View file

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-export-import-zip">
<resources>
<!-- Insert resources here -->
</resources>
<dependencies>
<module name="org.keycloak.keycloak-common"/>
<module name="org.keycloak.keycloak-core"/>
<module name="org.keycloak.keycloak-model-api"/>
<module name="org.keycloak.keycloak-invalidation-cache-model"/>
<module name="org.keycloak.keycloak-export-import-api"/>
<module name="javax.ws.rs.api"/>
<module name="org.codehaus.jackson.jackson-core-asl"/>
<module name="org.codehaus.jackson.jackson-mapper-asl"/>
<module name="org.codehaus.jackson.jackson-xc"/>
<module name="org.jboss.resteasy.resteasy-jaxrs"/>
<module name="org.jboss.logging"/>
<module name="org.bouncycastle" />
<module name="de.idyl.winzipaes"/>
<module name="javax.api"/>
</dependencies>
</module>

View file

@ -31,7 +31,6 @@
<module name="org.keycloak.keycloak-export-import-api" services="import"/>
<module name="org.keycloak.keycloak-export-import-dir" services="import"/>
<module name="org.keycloak.keycloak-export-import-single-file" services="import"/>
<module name="org.keycloak.keycloak-export-import-zip" services="import"/>
<module name="org.keycloak.keycloak-forms-common-freemarker" services="import"/>
<module name="org.keycloak.keycloak-forms-common-themes" services="import"/>
<module name="org.keycloak.keycloak-invalidation-cache-infinispan" services="import"/>

View file

@ -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>

View file

@ -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")

View file

@ -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()

View file

@ -14,7 +14,6 @@
<outputDirectory>modules/system/layers/base</outputDirectory>
<includes>
<include>com/google/zxing/**</include>
<include>de/idyl/winzipaes/**</include>
<include>org/freemarker/**</include>
<include>org/keycloak/**</include>
<include>org/liquibase/**</include>
@ -48,7 +47,13 @@
</includes>
<outputDirectory></outputDirectory>
</fileSet>
<!--<fileSet>
<directory>cli</directory>
<includes>
<include>*.cli</include>
</includes>
<outputDirectory>bin</outputDirectory>
</fileSet>-->
</fileSets>
<files>
@ -66,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>

View file

@ -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)

View file

@ -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")

View file

@ -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()

View file

@ -79,6 +79,19 @@
<section>
<title>Version specific migration</title>
<section>
<title>Migrating to 1.6.0.Final</title>
<simplesect>
<title>Refresh tokens are not reusable anymore</title>
<para>
Old versions of Keycloak allowed reusing refresh tokens multiple times. Keycloak no longer permits
this by default. When a refresh token is used to obtain a new access token a new refresh token is also
included. This new refresh token should be used next time the access token is refreshed. If this is
a problem for you it's possible to enable reuse of refresh tokens in the admin console under token
settings.
</para>
</simplesect>
</section>
<section>
<title>Migrating to 1.5.0.Final</title>
<simplesect>

View file

@ -8,12 +8,11 @@
<para>
You can export/import your database either to:
<itemizedlist>
<listitem>Encrypted ZIP file on local filesystem</listitem>
<listitem>Directory on local filesystem</listitem>
<listitem>Single JSON file on your filesystem</listitem>
</itemizedlist>
When importing using the "dir" or "zip" strategies, note that the files need to follow the naming convention specified below.
When importing using the "dir" strategy, note that the files need to follow the naming convention specified below.
If you are importing files which were previously exported, the files already follow this convention.
<itemizedlist>
<listitem>{REALM_NAME}-realm.json, such as "acme-roadrunner-affairs-realm.json" for the realm named "acme-roadrunner-affairs"</listitem>
@ -21,26 +20,10 @@
</itemizedlist>
</para>
<para>
Encrypted ZIP is recommended as export contains many sensitive informations like passwords of your users (even if they are hashed),
but also their email addresses, and especially private keys of the realms. Directory and Single JSON file are useful especially
for testing as data in the files are not protected. On the other hand, it's useful if you want to look at all your data in JSON
files directly.
</para>
<para>
If you import to ZIP or Directory, you can specify also the number of users to be stored in each JSON file. So if you have
If you import to Directory, you can specify also the number of users to be stored in each JSON file. So if you have
very large amount of users in your database, you likely don't want to import them into single file as the file might be very big.
Processing of each file is done in separate transaction as exporting/importing all users at once could also lead to memory issues.
</para>
<para>
So to export the content of your Keycloak database into encrypted ZIP, you can execute Keycloak server with the System properties like:
<programlisting><![CDATA[
bin/standalone.sh -Dkeycloak.migration.action=export
-Dkeycloak.migration.provider=zip -Dkeycloak.migration.zipFile=<FILE TO EXPORT TO>
-Dkeycloak.migration.zipPassword=<PASSWORD TO DECRYPT EXPORT>
]]></programlisting>
Then you can move or copy the encrypted ZIP file into second environment and you can trigger import from it into Keycloak server with the same command but use
<literal>-Dkeycloak.migration.action=import</literal> instead of <literal>export</literal> .
</para>
<para>
To export into unencrypted directory you can use:
<programlisting><![CDATA[
@ -80,7 +63,7 @@ bin/standalone.sh -Dkeycloak.migration.action=import
<term>-Dkeycloak.migration.usersExportStrategy</term>
<listitem>
<para>
can be used to specify for ZIP or Directory providers to specify where to import users.
can be used to specify for Directory providers to specify where to import users.
Possible values are:
<itemizedlist>
<listitem>DIFFERENT_FILES - Users will be exported into more different files according to maximum number of users per file. This is default value</listitem>

View file

@ -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>
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:
<programlisting>&lt;WILDFLY_HOME&gt;/bin/jboss-cli.sh -c --file=keycloak-install.cli</programlisting>
</para>
<!--<para>-->
<!--To add Keycloak to other sever configurations (standalone.xml, standalone-ha.xml, etc.) start the server with-->
<!--the desired server-config. If you are running the server in standalone mode run:-->
<!--<programlisting>&lt;WILDFLY_HOME&gt;/bin/jboss-cli.sh -c &#45;&#45;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 &#45;&#45;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 &#45;&#45;file=keycloak-install.cli</programlisting>-->
<!--</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>

View file

@ -70,6 +70,7 @@ public enum EventType {
CUSTOM_REQUIRED_ACTION(true),
CUSTOM_REQUIRED_ACTION_ERROR(true),
EXECUTE_ACTIONS(true),
EXECUTE_ACTIONS_ERROR(true),
CLIENT_INFO(false),
CLIENT_INFO_ERROR(false),

View file

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

View file

@ -1,67 +0,0 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>keycloak-export-import-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>1.6.0.Final-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-export-import-zip</artifactId>
<name>Keycloak Export Import To Encrypted ZIP</name>
<description/>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-export-import-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>de.idyl</groupId>
<artifactId>winzipaes</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View file

@ -1,82 +0,0 @@
package org.keycloak.exportimport.zip;
import de.idyl.winzipaes.AesZipFileEncrypter;
import de.idyl.winzipaes.impl.AESEncrypter;
import de.idyl.winzipaes.impl.AESEncrypterBC;
import org.jboss.logging.Logger;
import org.keycloak.representations.VersionRepresentation;
import org.keycloak.exportimport.util.ExportUtils;
import org.keycloak.exportimport.util.MultipleStepsExportProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.util.JsonSerialization;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.List;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ZipExportProvider extends MultipleStepsExportProvider {
private static final Logger logger = Logger.getLogger(ZipExportProvider.class);
private final AesZipFileEncrypter encrypter;
private final String password;
public ZipExportProvider(File zipFile, String password) {
if (zipFile.exists()) {
throw new IllegalStateException("File " + zipFile.getAbsolutePath() + " already exists");
}
this.password = password;
try {
AESEncrypter encrypter = new AESEncrypterBC();
this.encrypter = new AesZipFileEncrypter(zipFile, encrypter);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
logger.infof("Exporting into zip file %s", zipFile.getAbsolutePath());
}
@Override
protected void writeRealm(String fileName, RealmRepresentation rep) throws IOException {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
JsonSerialization.mapper.writeValue(stream, rep);
writeStream(fileName, stream);
}
@Override
protected void writeUsers(String fileName, KeycloakSession session, RealmModel realm, List<UserModel> users) throws IOException {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
ExportUtils.exportUsersToStream(session, realm, users, JsonSerialization.mapper, stream);
writeStream(fileName, stream);
}
@Override
protected void writeVersion(String fileName, VersionRepresentation version) throws IOException {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
JsonSerialization.mapper.writeValue(stream, version);
writeStream(fileName, stream);
}
private void writeStream(String fileName, ByteArrayOutputStream stream) throws IOException {
byte[] byteArray = stream.toByteArray();
ByteArrayInputStream bis = new ByteArrayInputStream(byteArray);
this.encrypter.add(fileName, bis, this.password);
}
@Override
public void close() {
try {
this.encrypter.close();
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
}
}

View file

@ -1,50 +0,0 @@
package org.keycloak.exportimport.zip;
import org.keycloak.Config;
import org.keycloak.exportimport.ExportImportConfig;
import org.keycloak.exportimport.ExportProvider;
import org.keycloak.exportimport.ExportProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import java.io.File;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ZipExportProviderFactory implements ExportProviderFactory {
public static final String PROVIDER_ID = "zip";
@Override
public ExportProvider create(KeycloakSession session) {
String fileName = ExportImportConfig.getZipFile();
String password = ExportImportConfig.getZipPassword();
if (fileName == null) {
throw new IllegalArgumentException("ZIP file for export not provided");
}
if (password == null) {
throw new IllegalArgumentException("Password for encrypting ZIP not provided");
}
return new ZipExportProvider(new File(fileName), password);
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return PROVIDER_ID;
}
}

View file

@ -1,135 +0,0 @@
package org.keycloak.exportimport.zip;
import de.idyl.winzipaes.AesZipFileDecrypter;
import de.idyl.winzipaes.impl.AESDecrypter;
import de.idyl.winzipaes.impl.AESDecrypterBC;
import de.idyl.winzipaes.impl.ExtZipEntry;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.exportimport.ImportProvider;
import org.keycloak.exportimport.Strategy;
import org.keycloak.exportimport.util.ExportImportSessionTask;
import org.keycloak.exportimport.util.ImportUtils;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.util.JsonSerialization;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.DataFormatException;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ZipImportProvider implements ImportProvider {
private static final Logger logger = Logger.getLogger(ZipImportProvider.class);
private final AesZipFileDecrypter decrypter;
private final String password;
public ZipImportProvider(File zipFile, String password) {
try {
if (!zipFile.exists()) {
throw new IllegalStateException("File " + zipFile.getAbsolutePath() + " doesn't exists");
}
AESDecrypter decrypter = new AESDecrypterBC();
this.decrypter = new AesZipFileDecrypter(zipFile, decrypter);
this.password = password;
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
logger.infof("Importing from ZIP file %s", zipFile.getAbsolutePath());
}
@Override
public void importModel(KeycloakSessionFactory factory, Strategy strategy) throws IOException {
List<String> realmNames = getRealmsToImport();
for (String realmName : realmNames) {
importRealm(factory, realmName, strategy);
}
}
@Override
public boolean isMasterRealmExported() throws IOException {
List<String> realmNames = getRealmsToImport();
return realmNames.contains(Config.getAdminRealm());
}
private List<String> getRealmsToImport() throws IOException {
List<String> realmNames = new ArrayList<String>();
for (ExtZipEntry entry : this.decrypter.getEntryList()) {
String entryName = entry.getName();
if (entryName.endsWith("-realm.json")) {
// Parse "foo" from "foo-realm.json"
String realmName = entryName.substring(0, entryName.length() - 11);
// Ensure that master realm is imported first
if (Config.getAdminRealm().equals(realmName)) {
realmNames.add(0, realmName);
} else {
realmNames.add(realmName);
}
}
}
return realmNames;
}
@Override
public void importRealm(KeycloakSessionFactory factory, final String realmName, final Strategy strategy) throws IOException {
try {
// Import realm first
ByteArrayOutputStream bos = new ByteArrayOutputStream();
this.decrypter.extractEntry(this.decrypter.getEntry(realmName + "-realm.json"), bos, this.password);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
final RealmRepresentation realmRep = JsonSerialization.mapper.readValue(bis, RealmRepresentation.class);
KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
@Override
protected void runExportImportTask(KeycloakSession session) throws IOException {
ImportUtils.importRealm(session, realmRep, strategy);
}
});
// Import users
for (ExtZipEntry entry : this.decrypter.getEntryList()) {
String name = entry.getName();
if (name.matches(realmName + "-users-[0-9]+\\.json")) {
bos = new ByteArrayOutputStream();
this.decrypter.extractEntry(entry, bos, this.password);
final ByteArrayInputStream bis2 = new ByteArrayInputStream(bos.toByteArray());
KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
@Override
protected void runExportImportTask(KeycloakSession session) throws IOException {
ImportUtils.importUsersFromStream(session, realmName, JsonSerialization.mapper, bis2);
}
});
}
}
} catch (DataFormatException dfe) {
throw new RuntimeException(dfe);
}
}
@Override
public void close() {
try {
this.decrypter.close();
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
}
}

View file

@ -1,47 +0,0 @@
package org.keycloak.exportimport.zip;
import org.keycloak.Config;
import org.keycloak.exportimport.ExportImportConfig;
import org.keycloak.exportimport.ImportProvider;
import org.keycloak.exportimport.ImportProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import java.io.File;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ZipImportProviderFactory implements ImportProviderFactory {
@Override
public ImportProvider create(KeycloakSession session) {
String fileName = ExportImportConfig.getZipFile();
String password = ExportImportConfig.getZipPassword();
if (fileName == null) {
throw new IllegalArgumentException("ZIP file for import not provided");
}
if (password == null) {
throw new IllegalArgumentException("Password for decrypting ZIP not provided");
}
return new ZipImportProvider(new File(fileName), password);
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return ZipExportProviderFactory.PROVIDER_ID;
}
}

View file

@ -1 +0,0 @@
org.keycloak.exportimport.zip.ZipExportProviderFactory

View file

@ -1 +0,0 @@
org.keycloak.exportimport.zip.ZipImportProviderFactory

View file

@ -18,7 +18,6 @@
<module>export-import-api</module>
<module>export-import-dir</module>
<module>export-import-single-file</module>
<module>export-import-zip</module>
</modules>
</project>

View file

@ -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);

View file

@ -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) {

View file

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

View file

@ -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));
}
}

View file

@ -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(" ")));
}
}

View file

@ -1,4 +1,4 @@
# TIPS to encode UTF-8 to ISO
# TIPS to encode UTF-8 to ISO
# native2ascii -encoding ISO8859_1 srcFile > dstFile
doSave=Sauvegarder
@ -45,19 +45,19 @@ role_view-users=Voir les utilisateurs
role_view-applications=Voir les applications
role_view-clients=Voir les clients
role_view-events=Voir les \u00e9v\u00e9nements
role_view-identity-providers=Voir les fournisseurs d'identit\u00e9s
role_view-identity-providers=Voir les fournisseurs d''identit\u00e9s
role_manage-realm=G\u00e9rer le domaine
role_manage-users=G\u00e9rer les utilisateurs
role_manage-applications=G\u00e9rer les applications
role_manage-identity-providers=G\u00e9rer les fournisseurs d'identit\u00e9s
role_manage-identity-providers=G\u00e9rer les fournisseurs d''identit\u00e9s
role_manage-clients=G\u00e9rer les clients
role_manage-events=G\u00e9rer les \u00e9v\u00e9nements
role_view-profile=Voir le profile
role_manage-account=G\u00e9rer le compte
role_read-token=Lire le jeton d'authentification
role_read-token=Lire le jeton d''authentification
role_offline-access=Acc\u00e9s hors-ligne
client_account=Compte
client_security-admin-console=Console d'administration de la s\u00e9curit\u00e9
client_security-admin-console=Console d''administration de la s\u00e9curit\u00e9
client_realm-management=Gestion du domaine
client_broker=Broker
@ -65,7 +65,7 @@ client_broker=Broker
requiredFields=Champs obligatoires
allFieldsRequired=Tous les champs obligatoires
backToApplication=&laquo; Revenir \u00e0 l'application
backToApplication=&laquo; Revenir \u00e0 l''application
backTo=Revenir \u00e0 {0}
date=Date
@ -86,68 +86,68 @@ sessions=Sessions
log=Connexion
application=Application
availablePermissions=Permissions Disponibles
availablePermissions=Permissions Disponibles
grantedPermissions=Permissions accord\u00e9es
grantedPersonalInfo=Informations personnels accord\u00e9es
additionalGrants=Droits additionnels
action=Action
inResource=dans
fullAccess=Acc\u00e9s complet
offlineToken=Jeton d'authentification hors-ligne
offlineToken=Jeton d''authentification hors-ligne
revoke=R\u00e9voquer un droit
configureAuthenticators=Authentifications configur\u00e9es.
mobile=T\u00e9l\u00e9phone mobile
totpStep1=Installlez <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> ou bien Google Authenticator sur votre mobile. Ces deux applications sont disponibles sur <a href="https://play.google.com">Google Play</a> et Apple App Store.
totpStep2=Ouvrez l'application et scanner le code bar ou entrez la clef.
totpStep3=Entrez le code \u00e0 usage unique fourni par l'application et cliquez sur Sauvegarder pour terminer.
totpStep1=Installez <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> ou bien Google Authenticator sur votre mobile. Ces deux applications sont disponibles sur <a href="https://play.google.com">Google Play</a> et Apple App Store.
totpStep2=Ouvrez l''application et scanner le code bar ou entrez la clef.
totpStep3=Entrez le code \u00e0 usage unique fourni par l''application et cliquez sur Sauvegarder pour terminer.
missingUsernameMessage=Veuillez entrez votre nom d'utilisateur.
missingFirstNameMessage=Veuillez entrez votre pr\u00e9nom.
missingUsernameMessage=Veuillez entrer votre nom d''utilisateur.
missingFirstNameMessage=Veuillez entrer votre pr\u00e9nom.
invalidEmailMessage=Courriel invalide.
missingLastNameMessage=Veuillez entrez votre nom.
missingEmailMessage=Veuillez entrez votre courriel.
missingPasswordMessage=Veuillez entrez votre mot de passe.
missingLastNameMessage=Veuillez entrer votre nom.
missingEmailMessage=Veuillez entrer votre courriel.
missingPasswordMessage=Veuillez entrer votre mot de passe.
notMatchPasswordMessage=Les mots de passe ne sont pas identiques
missingTotpMessage=Veuillez entrez le code authentification.
missingTotpMessage=Veuillez entrer le code authentification.
invalidPasswordExistingMessage=Mot de passe existant invalide.
invalidPasswordConfirmMessage=Le mot de passe de confirmation ne correspond pas.
invalidTotpMessage=Le code d'authentification est invalide.
invalidTotpMessage=Le code d''authentification est invalide.
usernameExistsMessage=Le nom d'utilisateur existe d\u00e9j\u00e0.
usernameExistsMessage=Le nom d''utilisateur existe d\u00e9j\u00e0.
emailExistsMessage=Le courriel existe d\u00e9j\u00e0.
readOnlyUserMessage=Vous ne pouvez pas mettre \u00e0 jour votre compte car il est en lecture seule.
readOnlyPasswordMessage=Vous ne pouvez pas mettre \u00e0 jour votre mot de passe car votre compte est en lecture seule.
successTotpMessage=L'authentification via t\u00e9l\u00e9phone mobile est configur\u00e9e.
successTotpRemovedMessage=L'authentification via t\u00e9l\u00e9phone mobile est supprim\u00e9e.
successTotpMessage=L''authentification via t\u00e9l\u00e9phone mobile est configur\u00e9e.
successTotpRemovedMessage=L''authentification via t\u00e9l\u00e9phone mobile est supprim\u00e9e.
successGrantRevokedMessage=Droit r\u00e9voqu\u00e9 avec succ\u00e8s.
accountUpdatedMessage=Votre compte a \u00e9t\u00e9 mis \u00e0 jour.
accountPasswordUpdatedMessage=Votre mot de passe a \u00e9t\u00e9 mis \u00e0 jour.
missingIdentityProviderMessage=Le fournisseur d'identit\u00e9 n'est pas sp\u00e9cifi\u00e9.
missingIdentityProviderMessage=Le fournisseur d''identit\u00e9 n''est pas sp\u00e9cifi\u00e9.
invalidFederatedIdentityActionMessage=Action manquante ou invalide.
identityProviderNotFoundMessage=Le fournisseur d'identit\u00e9 sp\u00e9cifi\u00e9 n'est pas trouv\u00e9.
federatedIdentityLinkNotActiveMessage=Cette identit\u00e9 n'est plus active dor\u00e9navant.
federatedIdentityRemovingLastProviderMessage=Vous ne pouvez pas supprimer votre derni\u00e8re f\u00e9d\u00e9ration d'identit\u00e9 sans avoir de mot de passe sp\u00e9cifi\u00e9.
identityProviderRedirectErrorMessage=Erreur de redirection vers le fournisseur d'identit\u00e9.
identityProviderRemovedMessage=Le fournisseur d'identit\u00e9 a \u00e9t\u00e9 supprim\u00e9 correctement.
identityProviderNotFoundMessage=Le fournisseur d''identit\u00e9 sp\u00e9cifi\u00e9 n''est pas trouv\u00e9.
federatedIdentityLinkNotActiveMessage=Cette identit\u00e9 n''est plus active dor\u00e9navant.
federatedIdentityRemovingLastProviderMessage=Vous ne pouvez pas supprimer votre derni\u00e8re f\u00e9d\u00e9ration d''identit\u00e9 sans avoir de mot de passe sp\u00e9cifi\u00e9.
identityProviderRedirectErrorMessage=Erreur de redirection vers le fournisseur d''identit\u00e9.
identityProviderRemovedMessage=Le fournisseur d''identit\u00e9 a \u00e9t\u00e9 supprim\u00e9 correctement.
accountDisabledMessage=Ce compte est d\u00e9sactiv\u00e9, veuillez contacter votre administrateur.
accountTemporarilyDisabledMessage=Ce compte est temporairement d\u00e9sactiv\u00e9, veuillez contacter votre administrateur ou r\u00e9essayez plus tard..
invalidPasswordMinLengthMessage=Mot de passe invalide: longueur minimale {0}.
invalidPasswordMinLowerCaseCharsMessage=Mot de passe invalide: doit contenir au moins {0} lettre(s) en minuscule.
invalidPasswordMinDigitsMessage=Mot de passe invalide: doit contenir au moins {0} chiffre(s).
invalidPasswordMinUpperCaseCharsMessage=Mot de passe invalide: doit contenir au moins {0} lettre(s) en majuscule.
invalidPasswordMinSpecialCharsMessage=Mot de passe invalide: doit contenir au moins {0} caractère(s) spéciaux.
invalidPasswordNotUsernameMessage=Mot de passe invalide: ne doit pas être identique au nom d'utilisateur.
invalidPasswordRegexPatternMessage=Mot de passe invalide: ne valide pas l'expression rationnelle.
invalidPasswordHistoryMessage=Mot de passe invalide: ne doit pas être égal aux {0} derniers mot de passe.
invalidPasswordMinLengthMessage=Mot de passe invalide : longueur minimale {0}.
invalidPasswordMinLowerCaseCharsMessage=Mot de passe invalide : doit contenir au moins {0} lettre(s) en minuscule.
invalidPasswordMinDigitsMessage=Mot de passe invalide : doit contenir au moins {0} chiffre(s).
invalidPasswordMinUpperCaseCharsMessage=Mot de passe invalide : doit contenir au moins {0} lettre(s) en majuscule.
invalidPasswordMinSpecialCharsMessage=Mot de passe invalide : doit contenir au moins {0} caract\u00e8re(s) sp\u00e9ciaux.
invalidPasswordNotUsernameMessage=Mot de passe invalide : ne doit pas etre identique au nom d''utilisateur.
invalidPasswordRegexPatternMessage=Mot de passe invalide : ne valide pas l''expression rationnelle.
invalidPasswordHistoryMessage=Mot de passe invalide : ne doit pas \u00eatre \u00e9gal aux {0} derniers mot de passe.
locale_de=German
locale_en=English

View file

@ -160,7 +160,7 @@ select-file=de Select file
view-details=de View details
clear-import=de Clear import
client-id.tooltip=de Specifies ID referenced in URI and tokens. For example 'my-client'
client.name.tooltip=de Specifies display name of the client. For example 'My Client'. Supports keys for localized values as well. For example: ${my_client}
client.name.tooltip=de Specifies display name of the client. For example 'My Client'. Supports keys for localized values as well. For example\: ${my_client}
client.enabled.tooltip=de Disabled clients cannot initiate a login or have obtain access tokens.
consent-required=de Consent Required
consent-required.tooltip=de If enabled users have to consent to client access.
@ -460,3 +460,4 @@ realm=de Realm
identity-provider-mappers=de Identity Provider Mappers
create-identity-provider-mapper=de Create Identity Provider Mapper
add-identity-provider-mapper=de Add Identity Provider Mapper
client.description.tooltip=de Specifies description of the client. For example 'My Client for TimeSheets'. Supports keys for localized values as well. For example\: ${my_client_description}

View file

@ -66,6 +66,8 @@ realm-cache-enabled=Realm Cache Enabled
realm-cache-enabled.tooltip=Enable/disable cache for realm, client and role data.
user-cache-enabled=User Cache Enabled
user-cache-enabled.tooltip=Enable/disable user and user role mapping cache.
revoke-refresh-token=Revoke Refresh Token
revoke-refresh-token.tooltip=If enabled refresh tokens can only be used once. Otherwise refresh tokens are not revoked when used and can be used multiple times.
sso-session-idle=SSO Session Idle
seconds=Seconds
minutes=Minutes
@ -160,7 +162,7 @@ select-file=Select file
view-details=View details
clear-import=Clear import
client-id.tooltip=Specifies ID referenced in URI and tokens. For example 'my-client'
client.name.tooltip=Specifies display name of the client. For example 'My Client'. Supports keys for localized values as well. For example: ${my_client}
client.name.tooltip=Specifies display name of the client. For example 'My Client'. Supports keys for localized values as well. For example\: ${my_client}
client.enabled.tooltip=Disabled clients cannot initiate a login or have obtain access tokens.
consent-required=Consent Required
consent-required.tooltip=If enabled users have to consent to client access.
@ -458,3 +460,4 @@ realm=Realm
identity-provider-mappers=Identity Provider Mappers
create-identity-provider-mapper=Create Identity Provider Mapper
add-identity-provider-mapper=Add Identity Provider Mapper
client.description.tooltip=Specifies description of the client. For example 'My Client for TimeSheets'. Supports keys for localized values as well. For example\: ${my_client_description}

View file

@ -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(" ");
};
});

View file

@ -38,6 +38,13 @@
</div>
<kc-tooltip>{{:: 'client.name.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="description">{{:: 'description' | translate}} </label>
<div class="col-sm-6">
<input class="form-control" type="text" id="description" name="description" data-ng-model="client.description">
</div>
<kc-tooltip>{{:: 'client.description.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix block">
<label class="col-md-2 control-label" for="enabled">{{:: 'enabled' | translate}}</label>
<div class="col-sm-6">

View file

@ -7,7 +7,7 @@
<div class="form-group">
<label class="col-md-2 control-label" for="alias">Alias </label>
<div class="col-sm-6">
<input class="form-control" type="text" id="alias" name="alias" data-ng-model="flow.alias" autofocus>
<input class="form-control" type="text" id="alias" name="alias" data-ng-model="flow.alias" autofocus required>
</div>
<kc-tooltip>Specifies display name for the flow.</kc-tooltip>
</div>

View file

@ -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">

View file

@ -3,6 +3,17 @@
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
<div class="form-group">
<label class="col-md-2 control-label" for="revokeRefreshToken">{{:: 'revoke-refresh-token' | translate}}</label>
<div class="col-md-6">
<input ng-model="realm.revokeRefreshToken" name="revokeRefreshToken" id="revokeRefreshToken" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
</div>
<kc-tooltip>{{:: 'revoke-refresh-token.tooltip' | translate}}
</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="ssoSessionIdleTimeout">{{:: 'sso-session-idle' | translate}}</label>

View file

@ -196,4 +196,5 @@ emailVerifiedMessage=Ihr E-Mail Adresse wurde erfolgreich verifiziert.
locale_de=Deutsch
locale_en=English
locale_it=Italian
locale_pt-BR=Portugu\u00EAs (Brasil)
locale_pt-BR=Portugu\u00EAs (Brasil)
locale_fr=Français

View file

@ -196,6 +196,7 @@ locale_de=German
locale_en=English
locale_it=Italian
locale_pt-BR=Portugu\u00EAs (Brasil)
locale_fr=Français
backToApplication=&laquo; Back to Application
missingParameterMessage=Missing parameters\: {0}

View file

@ -0,0 +1,205 @@
doLogIn=Connexion
doRegister=Enregistrement
doCancel=Annuler
doSubmit=Soumettre
doYes=Oui
doNo=Non
doContinue=Continuer
doAccept=Accepter
doDecline=Decliner
doForgotPassword=Mot de passe oublié ?
doClickHere=Cliquez ici
doImpersonate=Impersonate
kerberosNotConfigured=Kerberos non configuré
kerberosNotConfiguredTitle=Kerberos non configuré
bypassKerberosDetail=Si vous n''êtes pas connecté via Kerberos ou bie que votre navigateur n''est opas configurer pour la connexion via Kerberos. Veuillez cliquer pour vous connecter via un autre moyen.
kerberosNotSetUp=Kerberos n''est pas configuré. Connexion impossible.
registerWithTitle=Enregistrement avec {0}
registerWithTitleHtml=Enregistrement avec<strong>{0}</strong>
loginTitle=Connecté {0}
loginTitleHtml=Connecté <strong>{0}</strong>
impersonateTitle={0} utilisateur impersonate
impersonateTitleHtml=<strong>{0}</strong> utilisateur impersonate</strong>
realmChoice=Domaine
unknownUser=Utilisateur inconnu
loginTotpTitle=Configuration de l''authentification par mobile
loginProfileTitle=Mise à jour du compte
loginTimeout=Le temps imparti pour la connexion est écoulé. Le processus de connexion redémarre depuis le debut.
oauthGrantTitle=OAuth Grant
oauthGrantTitleHtml=Accès temporaire pour <strong>{0}</strong> demandé par
errorTitle=Nous sommes désolé ...
errorTitleHtml=Nous sommes <strong>désolé</strong> ...
emailVerifyTitle=Vérification du courriel
emailForgotTitle=Mot de passe oublié ?
updatePasswordTitle=Mise à jour du mot de passe
codeSuccessTitle=Code succès
codeErrorTitle=Code Erreur\: {0}
termsTitle=Termes et Conditions
termsTitleHtml=Termes et Conditions
termsText=<p>Termes et conditions à définir</p>
recaptchaFailed=Re-captcha invalide
recaptchaNotConfigured=Re-captcha est requis, mais il n''est pas configuré
consentDenied=Consentement refusé.
noAccount=Nouvel utilisateur?
username=Nom d''utilisateur
usernameOrEmail=Nom d''utilisateur ou courriel
firstName=Prénom
givenName=Prénom
fullName=Nom complet
lastName=Nom
familyName=Nom de famille
email=Courriel
password=Mot de passe
passwordConfirm=Confirmation du mot de passe
passwordNew=Nouveau mot de passe
passwordNewConfirm=Confirmation du nouveau not de passe
rememberMe=Se souvenir de moi
authenticatorCode=Code à usage unique
address=Adresse
street=Rue
locality=Ville ou Localité
region=État, Province, ou Région
postal_code=Code postal
country=Pays
emailVerified=Courriel vérifié
gssDelegationCredential=GSS Delegation Credential
loginTotpStep1=Installez <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> ou bien Google Authenticator sur votre mobile. Ces deux applications sont disponibles sur <a href="https://play.google.com">Google Play</a> et Apple App Store.
loginTotpStep2=Ouvrez l''application et scanner le code bar ou entrez la clef.
loginTotpStep3=Entrez le code \u00e0 usage unique fourni par l''application et cliquez sur Sauvegarder pour terminer.
loginTotpOneTime=Code à usage unique
oauthGrantRequest=Voulez-vous accorder ces privileges d''accès ?
inResource=dans
emailVerifyInstruction1=Un courriel avec des instructions à suivre vous a été envoyé.
emailVerifyInstruction2=Vous n''avez pas reçu de code dans le courriel ?
emailVerifyInstruction3=Pour renvoyer le courriel.
backToLogin=&laquo; Retour à la connexion
emailInstruction=Entrez votre nom d''utilisateur ou votre courriel, un email va vous \u00eatre envoyer vous permettant de créer un nouveau mot de passe.
copyCodeInstruction=Copiez le code et recopiez le dans votre application :
personalInfo=Information personnelle:
role_admin=Adminitrateur
role_realm-admin=Administrateur du domaine
role_create-realm=Cr\u00e9er un domaine
role_view-realm=Voir un domaine
role_view-users=Voir les utilisateurs
role_view-applications=Voir les applications
role_view-clients=Voir les clients
role_view-events=Voir les \u00e9v\u00e9nements
role_view-identity-providers=Voir les fournisseurs d''identit\u00e9s
role_manage-realm=G\u00e9rer le domaine
role_manage-users=G\u00e9rer les utilisateurs
role_manage-applications=G\u00e9rer les applications
role_manage-identity-providers=G\u00e9rer les fournisseurs d''identit\u00e9s
role_manage-clients=G\u00e9rer les clients
role_manage-events=G\u00e9rer les \u00e9v\u00e9nements
role_view-profile=Voir le profile
role_manage-account=G\u00e9rer le compte
role_read-token=Lire le jeton d''authentification
role_offline-access=Acc\u00e9s hors-ligne
client_account=Compte
client_security-admin-console=Console d''administration de la s\u00e9curit\u00e9
client_realm-management=Gestion du domaine
client_broker=Broker
invalidUserMessage=Nom d''utilisateur ou mot de passe invalide.
invalidEmailMessage=Adresse courriel invalide.
accountDisabledMessage=Compte désactivé, contactez votre administrateur.
accountTemporarilyDisabledMessage=Ce compte est temporairement désactivé, contactez votre administrateur ou bien réassayer plus tard.
expiredCodeMessage=Fin de connexion. Veuillez vous reconnecter.
missingFirstNameMessage=Veuillez entrer votre pr\u00e9nom.
missingLastNameMessage=Veuillez entrer votre nom.
missingEmailMessage=Veuillez entrer votre courriel.
missingUsernameMessage=Veuillez entrer votre nom d''utilisateur.
missingPasswordMessage=Veuillez entrer votre mot de passe.
missingTotpMessage=Veuillez entrer votre code d''authentification.
notMatchPasswordMessage=Les mots de passe ne sont pas identiques.
invalidPasswordExistingMessage=Mot de passe existant invalide.
invalidPasswordConfirmMessage=Le mot de passe de confirmation ne correspond pas.
invalidTotpMessage=Le code d''authentification est invalide.
usernameExistsMessage=Le nom d''utilisateur existe d\u00e9j\u00e0.
emailExistsMessage=Le courriel existe d\u00e9j\u00e0.
federatedIdentityEmailExistsMessage=Cet utilisateur avec ce courriel existe déjà. Veuillez vous connecté au gestionnaire de compte pour lier le compte.
federatedIdentityUsernameExistsMessage=Cet utilisateur avec ce nom d''utilisateur existe déjà. Veuillez vous connecté au gestionnaire de compte pour lier le compte.
configureTotpMessage=Vous devez configurer l''authentification par mobile pour activer votre compte.
updateProfileMessage=Vous devez mettre à jour votre profile pour activer votre compte.
updatePasswordMessage=Vous devez changer votre mot de passe pour activer votre compte.
verifyEmailMessage=Vous devez vérifier votre courriel pour activer votre compte.
emailSentMessage=Vous devriez recevoir rapidement un courriel avec de plus ample instructions.
emailSendErrorMessage=Erreur lors de l''envoie du courriel, veuillez essayer plus tard.
accountUpdatedMessage=Votre compte a été mis à jour.
accountPasswordUpdatedMessage=Votre mot de passe a été mis à jour.
noAccessMessage=Aucun accès
invalidPasswordMinLengthMessage=Mot de passe invalide : longueur minimale {0}.
invalidPasswordMinDigitsMessage=Mot de passe invalide : doit contenir au moins {0} chiffre(s).
invalidPasswordMinLowerCaseCharsMessage=Mot de passe invalide : doit contenir au moins {0} lettre(s) en minuscule.
invalidPasswordMinUpperCaseCharsMessage=Mot de passe invalide : doit contenir au moins {0} lettre(s) en majuscule.
invalidPasswordMinSpecialCharsMessage=Mot de passe invalide : doit contenir au moins {0} caract\u00e8re(s) sp\u00e9ciaux.
invalidPasswordNotUsernameMessage=Mot de passe invalide : ne doit pas etre identique au nom d''utilisateur.
invalidPasswordRegexPatternMessage=Mot de passe invalide : ne valide pas l''expression rationnelle.
invalidPasswordHistoryMessage=Mot de passe invalide : ne doit pas \u00eatre \u00e9gal aux {0} derniers mot de passe.
failedToProcessResponseMessage=Erreur lors du traitement de la réponse
httpsRequiredMessage=Le protocole HTTPS est requis
realmNotEnabledMessage=Le domaine n''est pas activé
invalidRequestMessage=Requete invalide
failedLogout=La déconnexion a échouée
unknownLoginRequesterMessage=Compte inconnu du demandeur
loginRequesterNotEnabledMessage=La connexion du demandeur n''est pas active
bearerOnlyMessage=Les applications Bearer-only ne sont pas autorisées à initier la connexion par navigateur.
directGrantsOnlyMessage=Les clients Direct-grants-only ne sont pas autorisés à initier la connexion par navigateur.
invalidRedirectUriMessage=L''uri de redirection est invalide
unsupportedNameIdFormatMessage=NameIDFormat non supporté
invlidRequesterMessage=Demandeur invalide
registrationNotAllowedMessage=L''enregistrement n''est pas autorisé
resetCredentialNotAllowedMessage=La remise \u00e0 z\u00e9ro n''est pas autorisé
permissionNotApprovedMessage=La permission n''est pas approuvée.
noRelayStateInResponseMessage=Aucun état de relais dans la réponse du fournisseur d''identité.
identityProviderAlreadyLinkedMessage=L''identité retournée par le fournisseur d''identité est déjà liée avec un autre utilisateur.
insufficientPermissionMessage=Permissions insuffisantes pour lier les identités.
couldNotProceedWithAuthenticationRequestMessage=Impossible de continuer avec la requête d''authentification vers le fournisseur d''identité.
couldNotObtainTokenMessage=Impossible de récuperer le jeton du fournisseur d''identité.
unexpectedErrorRetrievingTokenMessage=Erreur inattendue lors de la récupération du jeton provenant du fournisseur d''identité.
unexpectedErrorHandlingResponseMessage=Erreur inattendue lors du traitement de la réponse provenant du fournisseur d''identité.
identityProviderAuthenticationFailedMessage=L''authentification a échouée. Impossible de s''authentifier avec le fournisseur d''identité.
couldNotSendAuthenticationRequestMessage=Impossible d''envoyer la requête d''authentification vers le fournisseur d''identité.
unexpectedErrorHandlingRequestMessage=Erreur inattendue lors du traitement de la requête vers le fournisseur d''identité.
invalidAccessCodeMessage=Code d''accès invalide.
sessionNotActiveMessage=La session n''est pas active.
invalidCodeMessage=Une erreur est survenue, veuillez vous reconnecter à votre application.
identityProviderUnexpectedErrorMessage=Erreur inattendue lors de l''authentification avec fournisseur d''identité.
identityProviderNotFoundMessage=Impossible de trouver le fournisseur d''identité avec ce identifiant.
realmSupportsNoCredentialsMessage=Ce domaine ne supporte aucun type d''accréditation.
identityProviderNotUniqueMessage=Ce domaine autorise plusieurs fournisseurs d''identité. Impossible de déterminer fournisseurs d''identité avec lequel s''authentifier.
emailVerifiedMessage=Votre adresse courriel a été vérifiée.
locale_de=German
locale_en=English
locale_it=Italian
locale_pt-BR=Portugu\u00EAs (Brasil)
locale_fr=Français
backToApplication=&laquo; Revenir à l''application
missingParameterMessage=Paramètres manquants \: {0}
clientNotFoundMessage=Client inconnu.
invalidParameterMessage=Paramètres invalide \: {0}

View file

@ -189,6 +189,7 @@ locale_de=German
locale_en=English
locale_it=Italian
locale_pt-BR=Portugu\u00EAs (Brasil)
locale_fr=Français
backToApplication=&laquo; Torna all''Applicazione
missingParameterMessage=Parametri Mancanti\: {0}

View file

@ -194,6 +194,7 @@ locale_de=Deutsch
locale_en=English
locale_it=Italian
locale_pt-BR=Portugu\u00EAs (BR)
locale_fr=Français
backToApplication=&laquo; Voltar para o aplicativo
missingParameterMessage=Par\u00E2metros que faltam\: {0}

View file

@ -0,0 +1,21 @@
emailVerificationSubject=V\u00a9rification du courriel
emailVerificationBody=Quelqu''un vient de cr\u00a9er un compte "{2}" avec votre courriel. Si c''est bien vous, veuillez cliquer sur le lien ci-dessous afin de v\u00a9rifier votre adresse de courriel\n\n{0}\n\nCe lien expire dans {1} minute(s).\n\nSinon ignorer ce message.
emailVerificationBodyHtml=<p>Quelqu''un a cr\u00a9er un compte "{2}" avec votre courriel. Si c''est bien vous, veuillez cliquer sur le lien ci-dessous afin de v\u00a9rifier votre adresse de courriel</p><p><a href="{0}">{0}</a></p><p>Ce lien expire dans {1} minute(s).</p><p>Sinon veuillez ignorer ce message.</p>
passwordResetSubject=R\u00a9initialiser le mot de passe
passwordResetBody=Quelqu''un vient de demander une reinitialisation de mot de passe de votre compte {2}. Si c''est bien vous, veuillez cliquer sur le lien ci-dessous pour le mettre \u00a0 jour .\n\n{0}\n\nCe lien expire dans {1} minute(s).\n\nSinon ignorer ce message, aucun changement ne sera effectuer sur votre compte.
passwordResetBodyHtml=<p>Quelqu''un vient de demander une reinitialisation de mot de passe de votre compte {2}. Si c''est bien vous, veuillez cliquer sur le lien ci-dessous pour le mettre \u00a0 jour.</p><p><a href="{0}">{0}</a></p><p>Ce lien expire dans {1} minute(s).</p><p>Sinon ignorer ce message, aucun changement ne sera effectuer sur votre compte.</p>
executeActionsSubject=Mettre \u00a0 jour votre compte
executeActionsBody=Votre adminitrateurvient de demander une mise \u00a0 jour de votre compte {2}. veuillez cliquer sur le lien ci-dessous afin commencer le processus.\n\n{0}\n\nCe lien expire dans {1} minute(s).\n\nSi vous n''etes pas au courant de cette requete, ignorer ce message, aucun changement ne sera effectuer sur votre compte.
executeActionsBodyHtml=<p>Votre adminitrateurvient de demander une mise \u00a0 jour de votre compte {2}. veuillez cliquer sur le lien ci-dessous afin commencer le processus.</p><p><a href="{0}">{0}</a></p><p>Ce lien expire dans {1} minute(s).</p><p>Si vous n''etes pas au courant de cette requete, ignorer ce message, aucun changement ne sera effectuer sur votre compte.</p>
eventLoginErrorSubject=Erreur de connexion
eventLoginErrorBody=Une tentative de connexion a \u00a9t\u00a9 d\u00a9tect\u00a9e pour votre compte sur {0} depuis {1}. Si ce n''est pas vous, veuillez contacter votre administrateur.
eventLoginErrorBodyHtml=<p>Une tentative de connexion a \u00a9t\u00a9 d\u00a9tect\u00a9e pour votre compte sur {0} depuis {1}. Si ce n''est pas vous, veuillez contacter votre administrateur.</p>
eventRemoveTotpSubject=Suppression du TOTP
eventRemoveTotpBody=Le TOTP a \u00a9t\u00a9 supprim\u00a9 de votre compte sur {0} depuis {1}. Si ce n''est pas vous, veuillez contacter votre administrateur.
eventRemoveTotpBodyHtml=<p>Le TOTP a \u00a9t\u00a9 supprim\u00a9 de votre compte sur {0} depuis {1}. Si ce n''est pas vous, veuillez contacter votre administrateur.</p>
eventUpdatePasswordSubject=Mise \u00a0 jour du mot de passe
eventUpdatePasswordBody=Votre mot de passe \u00a0 chang\u00a9 sur {0} depuis {1}. Si ce n''est pas vous, veuillez contacter votre administrateur.
eventUpdatePasswordBodyHtml=<p>Votre mot de passe \u00a0 chang\u00a9 sur {0} depuis {1}. Si ce n''est pas vous, veuillez contacter votre administrateur.</p>
eventUpdateTotpSubject=Mise \u00a0 jour du TOTP
eventUpdateTotpBody=Le TOTP a \u00a9t\u00a9 mis \u00a0 jour pour votre compte sur {0} depuis {1}. Si ce n''est pas vous, veuillez contacter votre administrateur.
eventUpdateTotpBodyHtml=<p>Le TOTP a \u00a9t\u00a9 mis \u00a0 jour pour votre compte sur {0} depuis {1}. Si ce n''est pas vous, veuillez contacter votre administrateur.</p>

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>

View file

@ -20,11 +20,11 @@ import org.jboss.logging.Logger;
import org.keycloak.email.EmailException;
import org.keycloak.email.EmailProvider;
import org.keycloak.email.freemarker.beans.EventBean;
import org.keycloak.email.freemarker.beans.ProfileBean;
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;
@ -64,6 +64,7 @@ public class FreeMarkerEmailProvider implements EmailProvider {
@Override
public void sendEvent(Event event) throws EmailException {
Map<String, Object> attributes = new HashMap<String, Object>();
attributes.put("user", new ProfileBean(user));
attributes.put("event", new EventBean(event));
send(toCamelCase(event.getType()) + "Subject", "event-" + event.getType().toString().toLowerCase() + ".ftl", attributes);
@ -72,6 +73,7 @@ public class FreeMarkerEmailProvider implements EmailProvider {
@Override
public void sendPasswordReset(String link, long expirationInMinutes) throws EmailException {
Map<String, Object> attributes = new HashMap<String, Object>();
attributes.put("user", new ProfileBean(user));
attributes.put("link", link);
attributes.put("linkExpiration", expirationInMinutes);
@ -84,6 +86,7 @@ public class FreeMarkerEmailProvider implements EmailProvider {
@Override
public void sendExecuteActions(String link, long expirationInMinutes) throws EmailException {
Map<String, Object> attributes = new HashMap<String, Object>();
attributes.put("user", new ProfileBean(user));
attributes.put("link", link);
attributes.put("linkExpiration", expirationInMinutes);
@ -97,6 +100,7 @@ public class FreeMarkerEmailProvider implements EmailProvider {
@Override
public void sendVerifyEmail(String link, long expirationInMinutes) throws EmailException {
Map<String, Object> attributes = new HashMap<String, Object>();
attributes.put("user", new ProfileBean(user));
attributes.put("link", link);
attributes.put("linkExpiration", expirationInMinutes);
@ -110,7 +114,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));

View file

@ -0,0 +1,77 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2012, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.keycloak.email.freemarker.beans;
import org.jboss.logging.Logger;
import org.keycloak.models.UserModel;
import javax.ws.rs.core.MultivaluedMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
* @author Vlastimil Elias (velias at redhat dot com)
*/
public class ProfileBean {
private static final Logger logger = Logger.getLogger(ProfileBean.class);
private UserModel user;
private final Map<String, String> attributes = new HashMap<>();
public ProfileBean(UserModel user) {
this.user = user;
if (user.getAttributes() != null) {
for (Map.Entry<String, List<String>> attr : user.getAttributes().entrySet()) {
List<String> attrValue = attr.getValue();
if (attrValue != null && attrValue.size() > 0) {
attributes.put(attr.getKey(), attrValue.get(0));
}
if (attrValue != null && attrValue.size() > 1) {
logger.warnf("There are more values for attribute '%s' of user '%s' . Will display just first value", attr.getKey(), user.getUsername());
}
}
}
}
public String getUsername() { return user.getUsername(); }
public String getFirstName() {
return user.getFirstName();
}
public String getLastName() {
return user.getLastName();
}
public String getEmail() {
return user.getEmail();
}
public Map<String, String> getAttributes() {
return attributes;
}
}

View file

@ -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);

View file

@ -28,6 +28,10 @@ public interface ClientModel extends RoleContainerModel {
void setName(String name);
String getDescription();
void setDescription(String description);
boolean isEnabled();
void setEnabled(boolean enabled);

View file

@ -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);
}

View file

@ -91,12 +91,18 @@ public interface RealmModel extends RoleContainerModel {
void setResetPasswordAllowed(boolean resetPasswordAllowed);
boolean isRevokeRefreshToken();
void setRevokeRefreshToken(boolean revokeRefreshToken);
int getSsoSessionIdleTimeout();
void setSsoSessionIdleTimeout(int seconds);
int getSsoSessionMaxLifespan();
void setSsoSessionMaxLifespan(int seconds);
// int getOfflineSessionIdleTimeout();
// void setOfflineSessionIdleTimeout(int seconds);
int getAccessTokenLifespan();
void setAccessTokenLifespan(int seconds);
@ -286,6 +292,10 @@ public interface RealmModel extends RoleContainerModel {
void setEventsEnabled(boolean enabled);
// boolean isPersistUserSessions();
//
// void setPersistUserSessions();
long getEventsExpiration();
void setEventsExpiration(long expiration);

View file

@ -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() {
}

View file

@ -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();

View file

@ -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();
}

View file

@ -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

View file

@ -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();
}

View file

@ -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);
}

View file

@ -12,6 +12,7 @@ public class ClientEntity extends AbstractIdentifiableEntity {
private String clientId;
private String name;
private String description;
private String realmId;
private boolean enabled;
private String clientAuthenticatorType;
@ -61,6 +62,10 @@ public class ClientEntity extends AbstractIdentifiableEntity {
this.name = name;
}
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public boolean isEnabled() {
return enabled;
}

View file

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

View file

@ -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;

View file

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

View file

@ -39,6 +39,7 @@ public class RealmEntity extends AbstractIdentifiableEntity {
private int failureFactor;
//--- end brute force settings
private boolean revokeRefreshToken;
private int ssoSessionIdleTimeout;
private int ssoSessionMaxLifespan;
private int accessTokenLifespan;
@ -229,6 +230,14 @@ public class RealmEntity extends AbstractIdentifiableEntity {
this.failureFactor = failureFactor;
}
public boolean isRevokeRefreshToken() {
return revokeRefreshToken;
}
public void setRevokeRefreshToken(boolean revokeRefreshToken) {
this.revokeRefreshToken = revokeRefreshToken;
}
public int getSsoSessionIdleTimeout() {
return ssoSessionIdleTimeout;
}

View file

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

View file

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

View file

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

View file

@ -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;

View file

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

View file

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

View file

@ -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);
}

View file

@ -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> {
}

View file

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

View file

@ -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;
@ -144,6 +144,7 @@ public class ModelToRepresentation {
rep.setVerifyEmail(realm.isVerifyEmail());
rep.setResetPasswordAllowed(realm.isResetPasswordAllowed());
rep.setEditUsernameAllowed(realm.isEditUsernameAllowed());
rep.setRevokeRefreshToken(realm.isRevokeRefreshToken());
rep.setAccessTokenLifespan(realm.getAccessTokenLifespan());
rep.setSsoSessionIdleTimeout(realm.getSsoSessionIdleTimeout());
rep.setSsoSessionMaxLifespan(realm.getSsoSessionMaxLifespan());
@ -250,11 +251,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());
@ -299,6 +300,7 @@ public class ModelToRepresentation {
rep.setId(clientModel.getId());
rep.setClientId(clientModel.getClientId());
rep.setName(clientModel.getName());
rep.setDescription(clientModel.getDescription());
rep.setEnabled(clientModel.isEnabled());
rep.setAdminUrl(clientModel.getManagementUrl());
rep.setPublicClient(clientModel.isPublicClient());
@ -511,13 +513,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 +527,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();

View file

@ -90,14 +90,7 @@ public class Pbkdf2PasswordEncoder {
public static byte[] getSalt() {
byte[] buffer = new byte[16];
SecureRandom secureRandom;
try {
secureRandom = SecureRandom.getInstance(RNG_ALGORITHM);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("RNG algorithm not found");
}
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(buffer);
return buffer;

View file

@ -1,9 +1,5 @@
package org.keycloak.models.utils;
import org.keycloak.models.OfflineClientSessionModel;
import org.keycloak.models.OfflineUserSessionModel;
import org.keycloak.representations.idm.OfflineClientSessionRepresentation;
import org.keycloak.representations.idm.OfflineUserSessionRepresentation;
import org.keycloak.util.Base64;
import org.jboss.logging.Logger;
import org.keycloak.enums.SslRequired;
@ -100,6 +96,9 @@ public class RepresentationToModel {
if (rep.getNotBefore() != null) newRealm.setNotBefore(rep.getNotBefore());
if (rep.getRevokeRefreshToken() != null) newRealm.setRevokeRefreshToken(rep.getRevokeRefreshToken());
else newRealm.setRevokeRefreshToken(false);
if (rep.getAccessTokenLifespan() != null) newRealm.setAccessTokenLifespan(rep.getAccessTokenLifespan());
else newRealm.setAccessTokenLifespan(300);
@ -532,6 +531,7 @@ public class RepresentationToModel {
if (rep.getAccessCodeLifespanUserAction() != null) realm.setAccessCodeLifespanUserAction(rep.getAccessCodeLifespanUserAction());
if (rep.getAccessCodeLifespanLogin() != null) realm.setAccessCodeLifespanLogin(rep.getAccessCodeLifespanLogin());
if (rep.getNotBefore() != null) realm.setNotBefore(rep.getNotBefore());
if (rep.getRevokeRefreshToken() != null) realm.setRevokeRefreshToken(rep.getRevokeRefreshToken());
if (rep.getAccessTokenLifespan() != null) realm.setAccessTokenLifespan(rep.getAccessTokenLifespan());
if (rep.getSsoSessionIdleTimeout() != null) realm.setSsoSessionIdleTimeout(rep.getSsoSessionIdleTimeout());
if (rep.getSsoSessionMaxLifespan() != null) realm.setSsoSessionMaxLifespan(rep.getSsoSessionMaxLifespan());
@ -692,6 +692,7 @@ public class RepresentationToModel {
ClientModel client = resourceRep.getId()!=null ? realm.addClient(resourceRep.getId(), resourceRep.getClientId()) : realm.addClient(resourceRep.getClientId());
if (resourceRep.getName() != null) client.setName(resourceRep.getName());
if(resourceRep.getDescription() != null) client.setDescription(resourceRep.getDescription());
if (resourceRep.isEnabled() != null) client.setEnabled(resourceRep.isEnabled());
client.setManagementUrl(resourceRep.getAdminUrl());
if (resourceRep.isSurrogateAuthRequired() != null)
@ -793,6 +794,7 @@ public class RepresentationToModel {
public static void updateClient(ClientRepresentation rep, ClientModel resource) {
if (rep.getClientId() != null) resource.setClientId(rep.getClientId());
if (rep.getName() != null) resource.setName(rep.getName());
if (rep.getDescription() != null) resource.setDescription(rep.getDescription());
if (rep.isEnabled() != null) resource.setEnabled(rep.isEnabled());
if (rep.isBearerOnly() != null) resource.setBearerOnly(rep.isBearerOnly());
if (rep.isConsentRequired() != null) resource.setConsentRequired(rep.isConsentRequired());
@ -985,11 +987,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 +1157,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();

View file

@ -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;

View file

@ -0,0 +1 @@
org.keycloak.models.session.DisabledUserSessionPersisterProvider

View file

@ -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

View file

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

View file

@ -78,6 +78,12 @@ public class ClientAdapter implements ClientModel {
entity.setName(name);
}
@Override
public String getDescription() { return entity.getDescription(); }
@Override
public void setDescription(String description) { entity.setDescription(description); }
@Override
public Set<String> getWebOrigins() {
Set<String> result = new HashSet<String>();

View file

@ -325,6 +325,16 @@ public class RealmAdapter implements RealmModel {
}
@Override
public boolean isRevokeRefreshToken() {
return realm.isRevokeRefreshToken();
}
@Override
public void setRevokeRefreshToken(boolean revokeRefreshToken) {
realm.setRevokeRefreshToken(revokeRefreshToken);
}
@Override
public int getSsoSessionIdleTimeout() {
return realm.getSsoSessionIdleTimeout();

View file

@ -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.
*

View file

@ -321,6 +321,18 @@ public class ClientAdapter implements ClientModel {
updated.setName(name);
}
@Override
public String getDescription() {
if (updated != null) return updated.getDescription();
return cached.getDescription();
}
@Override
public void setDescription(String description) {
getDelegateForUpdate();
updated.setDescription(description);
}
@Override
public boolean isSurrogateAuthRequired() {
if (updated != null) return updated.isSurrogateAuthRequired();

View file

@ -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);
}
}

View file

@ -239,6 +239,18 @@ public class RealmAdapter implements RealmModel {
updated.setEditUsernameAllowed(editUsernameAllowed);
}
@Override
public boolean isRevokeRefreshToken() {
if (updated != null) return updated.isRevokeRefreshToken();
return cached.isRevokeRefreshToken();
}
@Override
public void setRevokeRefreshToken(boolean revokeRefreshToken) {
getDelegateForUpdate();
updated.setRevokeRefreshToken(revokeRefreshToken);
}
@Override
public int getSsoSessionIdleTimeout() {
if (updated != null) return updated.getSsoSessionIdleTimeout();

View file

@ -25,6 +25,7 @@ public class CachedClient implements Serializable {
private String id;
private String clientId;
private String name;
private String description;
private String realm;
private Set<String> redirectUris = new HashSet<String>();
private boolean enabled;
@ -58,6 +59,7 @@ public class CachedClient implements Serializable {
secret = model.getSecret();
clientId = model.getClientId();
name = model.getName();
description = model.getDescription();
this.realm = realm.getId();
enabled = model.isEnabled();
protocol = model.getProtocol();
@ -103,6 +105,10 @@ public class CachedClient implements Serializable {
return name;
}
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public String getRealm() {
return realm;
}

Some files were not shown because too many files have changed in this diff Show more