keycloak-uncached

Changes

distribution/server-overlay/eap6/eap6-server-overlay/cli/keycloak-prepare.cli 7(+0 -7)

distribution/server-overlay/wf9-server-overlay/cli/keycloak-prepare.cli 6(+0 -6)

Details

diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.6.0.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.6.0.xml
index a782f76..9e48a6a 100644
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.6.0.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.6.0.xml
@@ -10,6 +10,7 @@
 
         <addColumn tableName="CLIENT">
             <column name="ROOT_URL" type="VARCHAR(255)"/>
+            <column name="DESCRIPTION" type="VARCHAR(255)"/>
         </addColumn>
 
         <createTable tableName="OFFLINE_USER_SESSION">
@@ -47,5 +48,11 @@
 
         <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>
\ No newline at end of file
diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java
index 7ad8c6e..0999505 100755
--- a/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java
@@ -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;
     }
diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
index c1c53b3..1bec10b 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -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;
     }
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json b/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json
index 669cf41..3e4315c 100644
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json
@@ -59,7 +59,7 @@
 
     "connectionsInfinispan": {
         "default" : {
-            "cacheContainer" : "java:jboss/infinispan/Keycloak"
+            "cacheContainer" : "java:comp/env/infinispan/Keycloak"
         }
     }
 }
\ No newline at end of file
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/infinispan/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/infinispan/main/module.xml
new file mode 100755
index 0000000..65e3dc2
--- /dev/null
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/infinispan/main/module.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2014, 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.
+  -->
+
+<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-server-subsystem.infinispan">
+
+    <resources>
+        <!-- Insert resources here -->
+    </resources>
+
+    <dependencies>
+        <module name="org.infinispan" export="true"/>
+    </dependencies>
+</module>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml
index 11f8141..c339f44 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml
@@ -54,6 +54,9 @@
             <module name="org.jboss.resteasy.resteasy-jaxrs"/>
 
             <module name="org.jboss.msc"/>
+
+            <!-- suppress unsupported dependency 'org.infinispan:main' warning -->
+            <module name="org.keycloak.keycloak-server-subsystem.infinispan"/>
         </dependencies>
         <exclusions>
             <module name="org.jboss.resteasy.resteasy-jackson2-provider"/>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/web.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/web.xml
index 164f6be..f59d1d3 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/web.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/web.xml
@@ -39,4 +39,10 @@
         <servlet-name>Keycloak REST Interface</servlet-name>
         <url-pattern>/*</url-pattern>
     </servlet-mapping>
+
+    <resource-env-ref>
+        <resource-env-ref-name>infinispan/Keycloak</resource-env-ref-name>
+        <resource-env-ref-type>org.infinispan.manager.EmbeddedCacheManager</resource-env-ref-type>
+        <lookup-name>java:jboss/infinispan/Keycloak</lookup-name>
+    </resource-env-ref>
 </web-app>
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/build.xml b/distribution/server-overlay/eap6/eap6-server-modules/build.xml
index 2276471..60ccb65 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/build.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/build.xml
@@ -290,6 +290,8 @@
 
         <module-def name="org.keycloak.keycloak-server-subsystem"/>
 
+        <module-def name="org.keycloak.keycloak-server-subsystem.infinispan"/>
+
         <module-def name="org.keycloak.keycloak-wildfly-extensions">
             <maven-resource group="org.keycloak" artifact="keycloak-wildfly-extensions"/>
         </module-def>
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml
index 11f8141..c339f44 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml
@@ -54,6 +54,9 @@
             <module name="org.jboss.resteasy.resteasy-jaxrs"/>
 
             <module name="org.jboss.msc"/>
+
+            <!-- suppress unsupported dependency 'org.infinispan:main' warning -->
+            <module name="org.keycloak.keycloak-server-subsystem.infinispan"/>
         </dependencies>
         <exclusions>
             <module name="org.jboss.resteasy.resteasy-jackson2-provider"/>
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/web.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/web.xml
index 164f6be..f59d1d3 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/web.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/web.xml
@@ -39,4 +39,10 @@
         <servlet-name>Keycloak REST Interface</servlet-name>
         <url-pattern>/*</url-pattern>
     </servlet-mapping>
+
+    <resource-env-ref>
+        <resource-env-ref-name>infinispan/Keycloak</resource-env-ref-name>
+        <resource-env-ref-type>org.infinispan.manager.EmbeddedCacheManager</resource-env-ref-type>
+        <lookup-name>java:jboss/infinispan/Keycloak</lookup-name>
+    </resource-env-ref>
 </web-app>
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-server-subsystem/infinispan/main/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-server-subsystem/infinispan/main/module.xml
new file mode 100755
index 0000000..65e3dc2
--- /dev/null
+++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-server-subsystem/infinispan/main/module.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2014, 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.
+  -->
+
+<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-server-subsystem.infinispan">
+
+    <resources>
+        <!-- Insert resources here -->
+    </resources>
+
+    <dependencies>
+        <module name="org.infinispan" export="true"/>
+    </dependencies>
+</module>
diff --git a/distribution/server-overlay/eap6/eap6-server-overlay/assembly.xml b/distribution/server-overlay/eap6/eap6-server-overlay/assembly.xml
index 580b1d7..b3324df 100755
--- a/distribution/server-overlay/eap6/eap6-server-overlay/assembly.xml
+++ b/distribution/server-overlay/eap6/eap6-server-overlay/assembly.xml
@@ -20,13 +20,13 @@
                 <include>**/**</include>
             </includes>
         </fileSet>
-        <!--<fileSet>
+        <fileSet>
             <directory>cli</directory>
             <includes>
                 <include>*.cli</include>
             </includes>
             <outputDirectory>bin</outputDirectory>
-        </fileSet>-->
+        </fileSet>
     </fileSets>
 
     <files>
diff --git a/distribution/server-overlay/eap6/eap6-server-overlay/cli/keycloak-install.cli b/distribution/server-overlay/eap6/eap6-server-overlay/cli/keycloak-install.cli
index cc59431..156197a 100644
--- a/distribution/server-overlay/eap6/eap6-server-overlay/cli/keycloak-install.cli
+++ b/distribution/server-overlay/eap6/eap6-server-overlay/cli/keycloak-install.cli
@@ -1,2 +1,10 @@
+/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",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()
 /extension=org.keycloak.keycloak-server-subsystem/:add(module=org.keycloak.keycloak-server-subsystem)
-/subsystem=keycloak-server:add(web-context=auth)
\ No newline at end of file
+/subsystem=keycloak-server:add(web-context=auth)
+:shutdown(restart=true)
\ No newline at end of file
diff --git a/distribution/server-overlay/wf9-server-overlay/assembly.xml b/distribution/server-overlay/wf9-server-overlay/assembly.xml
index 0427f80..8f0daa2 100755
--- a/distribution/server-overlay/wf9-server-overlay/assembly.xml
+++ b/distribution/server-overlay/wf9-server-overlay/assembly.xml
@@ -47,13 +47,13 @@
             </includes>
             <outputDirectory></outputDirectory>
         </fileSet>
-        <!--<fileSet>
+        <fileSet>
             <directory>cli</directory>
             <includes>
                 <include>*.cli</include>
             </includes>
             <outputDirectory>bin</outputDirectory>
-        </fileSet>-->
+        </fileSet>
     </fileSets>
 
     <files>
diff --git a/distribution/server-overlay/wf9-server-overlay/cli/keycloak-install.cli b/distribution/server-overlay/wf9-server-overlay/cli/keycloak-install.cli
index cc59431..d6c3988 100644
--- a/distribution/server-overlay/wf9-server-overlay/cli/keycloak-install.cli
+++ b/distribution/server-overlay/wf9-server-overlay/cli/keycloak-install.cli
@@ -1,2 +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)
+/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()
 /extension=org.keycloak.keycloak-server-subsystem/:add(module=org.keycloak.keycloak-server-subsystem)
 /subsystem=keycloak-server:add(web-context=auth)
\ No newline at end of file
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml b/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml
index 10027b9..25a393f 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml
@@ -80,6 +80,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>
                 <title>Realm and User cache providers</title>
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml b/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml
index 9c78f3a..558f943 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml
@@ -43,7 +43,7 @@
 
 
         <section id="overlay_install">
-            <title>Install on existing WildFly 9.0.0.Final</title>
+            <title>Install on existing WildFly 9.0.1.Final</title>
             <para>
                 Keycloak can be installed into an existing WildFly 9.0.0.Final server. To do this download
                 <literal>keycloak-overlay-&project.version;.zip</literal> or <literal>keycloak-overlay-&project.version;.tar.gz</literal>.
@@ -59,22 +59,23 @@
                 (username: <emphasis>admin</emphasis> and password: <emphasis>admin</emphasis>). Keycloak will then prompt you to
                 enter in a new password.
             </para>
-            <!--<para>
+            <para>
                 To add Keycloak to other sever configurations (standalone.xml, standalone-ha.xml, etc.) start the server with
                 the desired server-config. If you are running the server in standalone mode run:
-                <programlisting>&lt;WILDFLY_HOME&gt;/bin/jboss-cli.sh -c --file=keycloak-prepare.cli</programlisting>
-                Or if you are running in clustering (HA) mode run:
-                <programlisting>&lt;WILDFLY_HOME&gt;/bin/jboss-cli.sh -c --file=keycloak-prepare-ha.cli</programlisting>
-                After that you need to restart the server, you can do this with:
+                <programlisting>cd &lt;WILDFLY_HOME&gt;/bin
+                    ./jboss-cli.sh -c --file=keycloak-install.cli</programlisting>
+                Or if you are running in clustering (HA) mode (by having used -c standalone-ha.xml) then run:
+                <programlisting>cd &lt;WILDFLY_HOME&gt;/bin
+                    ./jboss-cli.sh -c --file=keycloak-install-ha.cli</programlisting>
+                You may see exceptions in the server log, but after restarting the server they should be gone.
+                You can restart the server with:
                 <programlisting>&lt;WILDFLY_HOME&gt;/bin/jboss-cli.sh -c :reload</programlisting>
-                Finally you need to run:
-                <programlisting>&lt;WILDFLY_HOME&gt;/bin/jboss-cli.sh -c --file=keycloak-install.cli</programlisting>
-            </para>-->
+            </para>
         </section>
         <section>
             <title>Install on existing JBoss EAP 6.4.0.GA</title>
             <para>
-                Same procedure as WildFly 9.0.0.Final, but download <literal>keycloak-overlay-eap6-&project.version;.zip</literal> or <literal>keycloak-overlay-eap6-&project.version;.tar.gz</literal>.
+                Same procedure as WildFly 9.0.1.Final, but download <literal>keycloak-overlay-eap6-&project.version;.zip</literal> or <literal>keycloak-overlay-eap6-&project.version;.tar.gz</literal>.
             </para>
         </section>
         <section>
diff --git a/events/api/src/main/java/org/keycloak/events/EventType.java b/events/api/src/main/java/org/keycloak/events/EventType.java
index ac93a4f..0a8b3ae 100755
--- a/events/api/src/main/java/org/keycloak/events/EventType.java
+++ b/events/api/src/main/java/org/keycloak/events/EventType.java
@@ -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),
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_de.properties b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_de.properties
index 10549e1..87640e0 100644
--- a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_de.properties
+++ b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_de.properties
@@ -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}
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index d5193e7..802645e 100644
--- a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -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}
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
index 5d2b2dd..443cbe9 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
@@ -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">
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-tokens.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-tokens.html
index a44b939..bb55022 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-tokens.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-tokens.html
@@ -4,6 +4,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>
 
             <div class="col-md-6 time-selector">
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties
index 4cbbf0f..3445de5 100644
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties
@@ -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)
\ No newline at end of file
+locale_pt-BR=Portugu\u00EAs (Brasil)
+locale_fr=Français
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
index 65ad002..6fa441c 100644
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
@@ -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}
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_it.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_it.properties
index 4833b9b..e353153 100644
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_it.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_it.properties
@@ -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}
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties
index d2b1b65..e2a2257 100644
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties
@@ -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}
diff --git a/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/beans/ProfileBean.java b/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/beans/ProfileBean.java
new file mode 100755
index 0000000..812fb44
--- /dev/null
+++ b/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/beans/ProfileBean.java
@@ -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;
+    }
+}
diff --git a/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java b/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java
index f049085..9f55e37 100755
--- a/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java
+++ b/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java
@@ -20,6 +20,7 @@ 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;
@@ -63,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);
@@ -71,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);
 
@@ -83,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);
 
@@ -96,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);
 
diff --git a/model/api/src/main/java/org/keycloak/models/ClientModel.java b/model/api/src/main/java/org/keycloak/models/ClientModel.java
index ee4317c..8753f45 100755
--- a/model/api/src/main/java/org/keycloak/models/ClientModel.java
+++ b/model/api/src/main/java/org/keycloak/models/ClientModel.java
@@ -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);
diff --git a/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java b/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java
index 4c742ab..aa4f725 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java
@@ -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;
     }
diff --git a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
index 3dc43ef..e5fe6d2 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
@@ -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;
     }
diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index 452e2b3..7471a4c 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -91,6 +91,9 @@ public interface RealmModel extends RoleContainerModel {
 
     void setResetPasswordAllowed(boolean resetPasswordAllowed);
 
+    boolean isRevokeRefreshToken();
+    void setRevokeRefreshToken(boolean revokeRefreshToken);
+
     int getSsoSessionIdleTimeout();
     void setSsoSessionIdleTimeout(int seconds);
 
diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index 96b47cb..6b4a6be 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -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());
@@ -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());
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index 62933a0..6c61e66 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -1,9 +1,5 @@
 package org.keycloak.models.utils;
 
-import org.keycloak.models.session.PersistentClientSessionModel;
-import org.keycloak.models.session.PersistentUserSessionModel;
-import org.keycloak.representations.idm.OfflineClientSessionRepresentation;
-import org.keycloak.representations.idm.OfflineUserSessionRepresentation;
 import org.keycloak.util.Base64;
 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());
diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/ClientAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/ClientAdapter.java
index 1ddc364..81e60ee 100755
--- a/model/file/src/main/java/org/keycloak/models/file/adapter/ClientAdapter.java
+++ b/model/file/src/main/java/org/keycloak/models/file/adapter/ClientAdapter.java
@@ -79,6 +79,12 @@ public class ClientAdapter implements ClientModel {
     }
 
     @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>();
         if (entity.getWebOrigins() != null) {
diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java
index 5a62223..26227b1 100755
--- a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java
+++ b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java
@@ -326,6 +326,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();
     }
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
index e181978..477c5d6 100755
--- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
@@ -322,6 +322,18 @@ public class ClientAdapter implements ClientModel {
     }
 
     @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();
         return cached.isSurrogateAuthRequired();
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
index 54700e2..51d445c 100755
--- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
@@ -240,6 +240,18 @@ public class RealmAdapter implements RealmModel {
     }
 
     @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();
         return cached.getSsoSessionIdleTimeout();
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
index 3015acf..2d58222 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
@@ -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;
     }
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
index 27bab74..5193588 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
@@ -55,6 +55,7 @@ public class CachedRealm implements Serializable {
     private int failureFactor;
     //--- end brute force settings
 
+    private boolean revokeRefreshToken;
     private int ssoSessionIdleTimeout;
     private int ssoSessionMaxLifespan;
     private int accessTokenLifespan;
@@ -136,6 +137,7 @@ public class CachedRealm implements Serializable {
         failureFactor = model.getFailureFactor();
         //--- end brute force settings
 
+        revokeRefreshToken = model.isRevokeRefreshToken();
         ssoSessionIdleTimeout = model.getSsoSessionIdleTimeout();
         ssoSessionMaxLifespan = model.getSsoSessionMaxLifespan();
         accessTokenLifespan = model.getAccessTokenLifespan();
@@ -313,6 +315,10 @@ public class CachedRealm implements Serializable {
         return editUsernameAllowed;
     }
 
+    public boolean isRevokeRefreshToken() {
+        return revokeRefreshToken;
+    }
+
     public int getSsoSessionIdleTimeout() {
         return ssoSessionIdleTimeout;
     }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
index 1e0c21e..3baddd1 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
@@ -66,6 +66,12 @@ public class ClientAdapter implements ClientModel {
     }
 
     @Override
+    public String getDescription() { return entity.getDescription(); }
+
+    @Override
+    public void setDescription(String description) { entity.setDescription(description); }
+
+    @Override
     public boolean isEnabled() {
         return entity.isEnabled();
     }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
index b0a30cd..f26f651 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
@@ -34,6 +34,8 @@ public class ClientEntity {
     private String id;
     @Column(name = "NAME")
     private String name;
+    @Column(name = "DESCRIPTION")
+    private String description;
     @Column(name = "CLIENT_ID")
     private String clientId;
     @Column(name="ENABLED")
@@ -143,6 +145,14 @@ public class ClientEntity {
         this.name = name;
     }
 
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
     public boolean isEnabled() {
         return enabled;
     }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index fbfb700..27c4824 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -76,6 +76,8 @@ public class RealmEntity {
     @Column(name="EDIT_USERNAME_ALLOWED")
     protected boolean editUsernameAllowed;
 
+    @Column(name="REVOKE_REFRESH_TOKEN")
+    private boolean revokeRefreshToken;
     @Column(name="SSO_IDLE_TIMEOUT")
     private int ssoSessionIdleTimeout;
     @Column(name="SSO_MAX_LIFESPAN")
@@ -288,6 +290,14 @@ public class RealmEntity {
         this.editUsernameAllowed = editUsernameAllowed;
     }
 
+    public boolean isRevokeRefreshToken() {
+        return revokeRefreshToken;
+    }
+
+    public void setRevokeRefreshToken(boolean revokeRefreshToken) {
+        this.revokeRefreshToken = revokeRefreshToken;
+    }
+
     public int getSsoSessionIdleTimeout() {
         return ssoSessionIdleTimeout;
     }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 6268735..2c8e2ad 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -337,6 +337,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 getAccessTokenLifespan() {
         return realm.getAccessTokenLifespan();
     }
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
index 4e1b4fa..e99f142 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
@@ -71,6 +71,15 @@ public class ClientAdapter extends AbstractMongoAdapter<MongoClientEntity> imple
     }
 
     @Override
+    public String getDescription() { return getMongoEntity().getDescription(); }
+
+    @Override
+    public void setDescription(String description) {
+        getMongoEntity().setDescription(description);
+        updateMongoEntity();
+    }
+
+    @Override
     public void setClientId(String clientId) {
         getMongoEntity().setClientId(clientId);
         updateMongoEntity();
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index a6166cd..05ae8bc 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -311,6 +311,16 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
         updateRealm();
     }
 
+    @Override
+    public boolean isRevokeRefreshToken() {
+        return realm.isRevokeRefreshToken();
+    }
+
+    @Override
+    public void setRevokeRefreshToken(boolean revokeRefreshToken) {
+        realm.setRevokeRefreshToken(revokeRefreshToken);
+        updateRealm();
+    }
 
     @Override
     public int getSsoSessionIdleTimeout() {
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
index e899186..0888dab 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -161,6 +161,15 @@ public class TokenManager {
         }
 
         int currentTime = Time.currentTime();
+
+        if (realm.isRevokeRefreshToken() && !refreshToken.getType().equals(TokenUtil.TOKEN_TYPE_OFFLINE)) {
+            if (refreshToken.getIssuedAt() < validation.clientSession.getTimestamp()) {
+                throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale token");
+            }
+
+            validation.clientSession.setTimestamp(currentTime);
+        }
+
         validation.userSession.setLastSessionRefresh(currentTime);
 
         AccessTokenResponseBuilder responseBuilder = responseBuilder(realm, authorizedClient, event, session, validation.userSession, validation.clientSession)
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java
index e780dbf..5c80bca 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java
@@ -73,6 +73,7 @@ public class ClientsResource {
                 ClientRepresentation client = new ClientRepresentation();
                 client.setId(clientModel.getId());
                 client.setClientId(clientModel.getClientId());
+                client.setDescription(clientModel.getDescription());
                 rep.add(client);
             }
         }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AbstractClientTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AbstractClientTest.java
index cfc9270..e3d6a57 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AbstractClientTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AbstractClientTest.java
@@ -13,6 +13,8 @@ import org.keycloak.representations.idm.IdentityProviderRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.testsuite.rule.KeycloakRule;
+import org.keycloak.testsuite.rule.WebResource;
+import org.openqa.selenium.WebDriver;
 
 import java.util.Arrays;
 import java.util.Collections;
@@ -41,6 +43,7 @@ public abstract class AbstractClientTest {
             public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
                 RealmModel testRealm = manager.createRealm(REALM_NAME);
                 testRealm.setEnabled(true);
+                testRealm.setAccessCodeLifespanUserAction(600);
                 KeycloakModelUtils.generateRealmKeys(testRealm);
             }
         });
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ClientTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ClientTest.java
index 2097175..cf2df9d 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ClientTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ClientTest.java
@@ -48,6 +48,7 @@ public class ClientTest extends AbstractClientTest {
     private String createClient() {
         ClientRepresentation rep = new ClientRepresentation();
         rep.setClientId("my-app");
+        rep.setDescription("my-app description");
         rep.setEnabled(true);
         Response response = realm.clients().create(rep);
         response.close();
@@ -79,6 +80,19 @@ public class ClientTest extends AbstractClientTest {
         assertTrue(rep.isEnabled());
     }
 
+    /**
+     * See <a href="https://issues.jboss.org/browse/KEYCLOAK-1918">KEYCLOAK-1918</a>
+     */
+    @Test
+    public void getClientDescription() {
+
+        String id = createClient();
+
+        ClientRepresentation rep = realm.clients().get(id).toRepresentation();
+        assertEquals(id, rep.getId());
+        assertEquals("my-app description", rep.getDescription());
+    }
+
     @Test
     public void getClientSessions() throws Exception {
         OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password");
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java
index de1b2ca..f262f34 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java
@@ -1,18 +1,37 @@
 package org.keycloak.testsuite.admin;
 
 import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.admin.client.resource.IdentityProviderResource;
 import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.representations.idm.ErrorRepresentation;
 import org.keycloak.representations.idm.FederatedIdentityRepresentation;
 import org.keycloak.representations.idm.IdentityProviderRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
-
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.forms.ResetPasswordTest;
+import org.keycloak.testsuite.pages.LoginPasswordResetPage;
+import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
+import org.keycloak.testsuite.rule.GreenMailRule;
+import org.keycloak.testsuite.rule.KeycloakRule;
+import org.keycloak.testsuite.rule.WebResource;
+import org.keycloak.testsuite.rule.WebRule;
+import org.openqa.selenium.WebDriver;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+import javax.ws.rs.BadRequestException;
 import javax.ws.rs.ClientErrorException;
 import javax.ws.rs.core.Response;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
@@ -27,6 +46,31 @@ import static org.junit.Assert.fail;
  */
 public class UserTest extends AbstractClientTest {
 
+    @Rule
+    public WebRule webRule = new WebRule(this);
+
+    @Rule
+    public GreenMailRule greenMail = new GreenMailRule();
+
+    @WebResource
+    protected LoginPasswordUpdatePage passwordUpdatePage;
+
+    @WebResource
+    protected WebDriver driver;
+
+    @Before
+    public void before() {
+        super.before();
+
+        keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                RealmModel testRealm = manager.getRealm(REALM_NAME);
+                greenMail.configureRealm(testRealm);
+            }
+        });
+    }
+
     public String createUser() {
         return createUser("user1", "user1@localhost");
     }
@@ -397,6 +441,40 @@ public class UserTest extends AbstractClientTest {
     }
 
     @Test
+    public void sendResetPasswordEmailSuccess() throws IOException, MessagingException {
+        UserRepresentation userRep = new UserRepresentation();
+        userRep.setEnabled(true);
+        userRep.setUsername("user1");
+        userRep.setEmail("user1@test.com");
+        Response response = realm.users().create(userRep);
+        String id = ApiUtil.getCreatedId(response);
+        response.close();
+        UserResource user = realm.users().get(id);
+        List<String> actions = new LinkedList<>();
+        actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name());
+        user.executeActionsEmail("account", actions);
+
+        Assert.assertEquals(1, greenMail.getReceivedMessages().length);
+
+        MimeMessage message = greenMail.getReceivedMessages()[0];
+
+        String link = ResetPasswordTest.getPasswordResetEmailLink(message);
+
+        driver.navigate().to(link);
+
+        assertTrue(passwordUpdatePage.isCurrent());
+
+        passwordUpdatePage.changePassword("new-pass", "new-pass");
+
+        assertEquals("Your account has been updated.", driver.getTitle());
+
+        driver.navigate().to(link);
+
+        assertEquals("We're sorry...", driver.getTitle());
+    }
+
+
+    @Test
     public void sendVerifyEmail() {
         UserRepresentation userRep = new UserRepresentation();
         userRep.setUsername("user1");
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
index 788b788..ab47f49 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
@@ -630,7 +630,7 @@ public class ResetPasswordTest {
         }
     }
 
-    private String getPasswordResetEmailLink(MimeMessage message) throws IOException, MessagingException {
+    public static String getPasswordResetEmailLink(MimeMessage message) throws IOException, MessagingException {
     	Multipart multipart = (Multipart) message.getContent();
     	
         final String textContentType = multipart.getBodyPart(0).getContentType();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ClientModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ClientModelTest.java
index eff25e3..bb47ebe 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ClientModelTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ClientModelTest.java
@@ -32,6 +32,7 @@ public class ClientModelTest extends AbstractModelTest {
         realm = realmManager.createRealm("original");
         client = realm.addClient("application");
         client.setName("Application");
+        client.setDescription("Description");
         client.setBaseUrl("http://base");
         client.setManagementUrl("http://management");
         client.setClientId("app-name");
@@ -87,6 +88,7 @@ public class ClientModelTest extends AbstractModelTest {
     public static void assertEquals(ClientModel expected, ClientModel actual) {
         Assert.assertEquals(expected.getClientId(), actual.getClientId());
         Assert.assertEquals(expected.getName(), actual.getName());
+        Assert.assertEquals(expected.getDescription(), actual.getDescription());
         Assert.assertEquals(expected.getBaseUrl(), actual.getBaseUrl());
         Assert.assertEquals(expected.getManagementUrl(), actual.getManagementUrl());
         Assert.assertEquals(expected.getDefaultRoles(), actual.getDefaultRoles());
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java
index 363a1e9..742f5d6 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java
@@ -30,6 +30,7 @@ import org.keycloak.enums.SslRequired;
 import org.keycloak.events.Details;
 import org.keycloak.events.Errors;
 import org.keycloak.events.Event;
+import org.keycloak.events.EventType;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserSessionModel;
@@ -181,6 +182,93 @@ public class RefreshTokenTest {
         Time.setOffset(0);
     }
 
+    @Test
+    public void refreshTokenReuseTokenWithoutRefreshTokensRevoked() throws Exception {
+        try {
+            oauth.doLogin("test-user@localhost", "password");
+
+            Event loginEvent = events.expectLogin().assertEvent();
+
+            String sessionId = loginEvent.getSessionId();
+            String codeId = loginEvent.getDetails().get(Details.CODE_ID);
+
+            String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+
+            AccessTokenResponse response1 = oauth.doAccessTokenRequest(code, "password");
+            RefreshToken refreshToken1 = oauth.verifyRefreshToken(response1.getRefreshToken());
+
+            events.expectCodeToToken(codeId, sessionId).assertEvent();
+
+            Time.setOffset(2);
+
+            AccessTokenResponse response2 = oauth.doRefreshTokenRequest(response1.getRefreshToken(), "password");
+            Assert.assertEquals(200, response2.getStatusCode());
+
+            events.expectRefresh(refreshToken1.getId(), sessionId).assertEvent();
+
+            AccessTokenResponse response3 = oauth.doRefreshTokenRequest(response1.getRefreshToken(), "password");
+
+            Assert.assertEquals(200, response3.getStatusCode());
+
+            events.expectRefresh(refreshToken1.getId(), sessionId).assertEvent();
+        } finally {
+            Time.setOffset(0);
+        }
+    }
+
+    @Test
+    public void refreshTokenReuseTokenWithRefreshTokensRevoked() throws Exception {
+        try {
+            keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+                @Override
+                public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                    appRealm.setRevokeRefreshToken(true);
+                }
+            });
+
+            oauth.doLogin("test-user@localhost", "password");
+
+            Event loginEvent = events.expectLogin().assertEvent();
+
+            String sessionId = loginEvent.getSessionId();
+            String codeId = loginEvent.getDetails().get(Details.CODE_ID);
+
+            String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+
+            AccessTokenResponse response1 = oauth.doAccessTokenRequest(code, "password");
+            RefreshToken refreshToken1 = oauth.verifyRefreshToken(response1.getRefreshToken());
+
+            events.expectCodeToToken(codeId, sessionId).assertEvent();
+
+            Time.setOffset(2);
+
+            AccessTokenResponse response2 = oauth.doRefreshTokenRequest(response1.getRefreshToken(), "password");
+            RefreshToken refreshToken2 = oauth.verifyRefreshToken(response2.getRefreshToken());
+
+            Assert.assertEquals(200, response2.getStatusCode());
+
+            events.expectRefresh(refreshToken1.getId(), sessionId).assertEvent();
+
+            AccessTokenResponse response3 = oauth.doRefreshTokenRequest(response1.getRefreshToken(), "password");
+
+            Assert.assertEquals(400, response3.getStatusCode());
+
+            events.expectRefresh(refreshToken1.getId(), sessionId).removeDetail(Details.TOKEN_ID).removeDetail(Details.UPDATED_REFRESH_TOKEN_ID).error("invalid_token").assertEvent();
+
+            oauth.doRefreshTokenRequest(response2.getRefreshToken(), "password");
+
+            events.expectRefresh(refreshToken2.getId(), sessionId).assertEvent();
+        } finally {
+            Time.setOffset(0);
+            keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+                @Override
+                public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                    appRealm.setRevokeRefreshToken(false);
+                }
+            });
+        }
+    }
+
     PrivateKey privateKey;
     PublicKey publicKey;
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java
index 23cb7f2..c07766d 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java
@@ -11,10 +11,7 @@ import org.junit.Test;
 import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
 import org.keycloak.events.Details;
 import org.keycloak.events.Errors;
-import org.keycloak.models.ClientModel;
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.UserCredentialModel;
-import org.keycloak.models.UserModel;
+import org.keycloak.models.*;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.RefreshToken;
 import org.keycloak.services.managers.ClientManager;
@@ -24,6 +21,7 @@ import org.keycloak.testsuite.OAuthClient;
 import org.keycloak.testsuite.rule.KeycloakRule;
 import org.keycloak.testsuite.rule.WebResource;
 import org.keycloak.testsuite.rule.WebRule;
+import org.keycloak.util.Time;
 import org.openqa.selenium.WebDriver;
 
 import static org.junit.Assert.assertEquals;
@@ -233,7 +231,47 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
 
     }
 
+    @Test
+    public void grantAccessTokenExpiredPassword() throws Exception {
+        keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                appRealm.setPasswordPolicy(new PasswordPolicy("forceExpiredPasswordChange(1)"));
+            }
+        });
+
+        try {
+            Time.setOffset(60 * 60 * 48);
 
+            oauth.clientId("resource-owner");
+
+            OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "test-user@localhost", "password");
+
+            assertEquals(400, response.getStatusCode());
+
+            assertEquals("invalid_grant", response.getError());
+            assertEquals("Account is not fully set up", response.getErrorDescription());
+
+            events.expectLogin()
+                    .client("resource-owner")
+                    .session((String) null)
+                    .clearDetails()
+                    .error(Errors.RESOLVE_REQUIRED_ACTIONS)
+                    .user((String) null)
+                    .assertEvent();
+        } finally {
+            Time.setOffset(0);
+
+            keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+                @Override
+                public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                    appRealm.setPasswordPolicy(new PasswordPolicy(""));
+                    UserModel user = manager.getSession().users().getUserByEmail("test-user@localhost", appRealm);
+                    user.removeRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
+                }
+            });
+        }
+    }
 
 
     @Test
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/GreenMailRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/GreenMailRule.java
index bd52a59..21a35a6 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/GreenMailRule.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/GreenMailRule.java
@@ -24,10 +24,13 @@ package org.keycloak.testsuite.rule;
 import com.icegreen.greenmail.util.GreenMail;
 import com.icegreen.greenmail.util.ServerSetup;
 import org.junit.rules.ExternalResource;
+import org.keycloak.models.RealmModel;
 
 import javax.mail.internet.MimeMessage;
 import java.lang.Thread.UncaughtExceptionHandler;
 import java.net.SocketException;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -63,6 +66,14 @@ public class GreenMailRule extends ExternalResource {
         }
     }
 
+    public void configureRealm(RealmModel realm) {
+        Map<String, String> config = new HashMap<>();
+        config.put("from", "auto@keycloak.org");
+        config.put("host", "localhost");
+        config.put("port", "3025");
+        realm.setSmtpConfig(config);
+    }
+
     public MimeMessage[] getReceivedMessages() {
         return greenMail.getReceivedMessages();
     }