keycloak-aplcache

Changes

pom.xml 2(+1 -1)

testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/ContainersTest.java 34(+0 -34)

Details

diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/BasicAuthRequestAuthenticator.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/BasicAuthRequestAuthenticator.java
index 1d32f6e..81d4688 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/BasicAuthRequestAuthenticator.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/BasicAuthRequestAuthenticator.java
@@ -85,42 +85,38 @@ public class BasicAuthRequestAuthenticator extends BearerTokenRequestAuthenticat
     
     private AccessTokenResponse getToken(String username, String password) throws Exception {
     	AccessTokenResponse tokenResponse=null;
-    	HttpClient client = new HttpClientBuilder().disableTrustManager().build();
+    	HttpClient client = deployment.getClient();
 
-    	try {
-    	    HttpPost post = new HttpPost(
-    	            KeycloakUriBuilder.fromUri(deployment.getAuthServerBaseUrl())
-    	            .path(ServiceUrlConstants.TOKEN_PATH).build(deployment.getRealm()));
-    	    java.util.List <NameValuePair> formparams = new java.util.ArrayList <NameValuePair>();
-    	    formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD));
-    	    formparams.add(new BasicNameValuePair("username", username));
-    	    formparams.add(new BasicNameValuePair("password", password));
+        HttpPost post = new HttpPost(
+                KeycloakUriBuilder.fromUri(deployment.getAuthServerBaseUrl())
+                .path(ServiceUrlConstants.TOKEN_PATH).build(deployment.getRealm()));
+        java.util.List <NameValuePair> formparams = new java.util.ArrayList <NameValuePair>();
+        formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD));
+        formparams.add(new BasicNameValuePair("username", username));
+        formparams.add(new BasicNameValuePair("password", password));
 
-			ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
+        ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
 
-    	    UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
-    	    post.setEntity(form);
+        UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
+        post.setEntity(form);
 
-    	    HttpResponse response = client.execute(post);
-    	    int status = response.getStatusLine().getStatusCode();
-    	    HttpEntity entity = response.getEntity();
-    	    if (status != 200) {
-    	        throw new java.io.IOException("Bad status: " + status);
-    	    }
-    	    if (entity == null) {
-    	        throw new java.io.IOException("No Entity");
-    	    }
-    	    java.io.InputStream is = entity.getContent();
-    	    try {
-    	        tokenResponse = JsonSerialization.readValue(is, AccessTokenResponse.class);
-    	    } finally {
-    	        try {
-    	            is.close();
-    	        } catch (java.io.IOException ignored) { }
-    	    }
-    	} finally {
-    	    client.getConnectionManager().shutdown();
-    	}
+        HttpResponse response = client.execute(post);
+        int status = response.getStatusLine().getStatusCode();
+        HttpEntity entity = response.getEntity();
+        if (status != 200) {
+            throw new java.io.IOException("Bad status: " + status);
+        }
+        if (entity == null) {
+            throw new java.io.IOException("No Entity");
+        }
+        java.io.InputStream is = entity.getContent();
+        try {
+            tokenResponse = JsonSerialization.readValue(is, AccessTokenResponse.class);
+        } finally {
+            try {
+                is.close();
+            } catch (java.io.IOException ignored) { }
+        }
     	
     	return (tokenResponse);
     }
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
index 1ff38c7..ab77491 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
@@ -98,7 +98,7 @@ public class KeycloakDeploymentBuilder {
         if (realmKeyPem == null && adapterConfig.isBearerOnly() && adapterConfig.getAuthServerUrl() == null) {
             throw new IllegalArgumentException("For bearer auth, you must set the realm-public-key or auth-server-url");
         }
-        if (realmKeyPem == null || !deployment.isBearerOnly() || deployment.isRegisterNodeAtStartup() || deployment.getRegisterNodePeriod() != -1) {
+        if (realmKeyPem == null || !deployment.isBearerOnly() || deployment.isEnableBasicAuth() || deployment.isRegisterNodeAtStartup() || deployment.getRegisterNodePeriod() != -1) {
             deployment.setClient(new HttpClientBuilder().build(adapterConfig));
         }
         if (adapterConfig.getAuthServerUrl() == null && (!deployment.isBearerOnly() || realmKeyPem == null)) {
diff --git a/common/src/main/java/org/keycloak/common/Version.java b/common/src/main/java/org/keycloak/common/Version.java
index 8041138..b1b9705 100755
--- a/common/src/main/java/org/keycloak/common/Version.java
+++ b/common/src/main/java/org/keycloak/common/Version.java
@@ -45,14 +45,10 @@ public class Version {
             Version.VERSION = props.getProperty("version");
             Version.BUILD_TIME = props.getProperty("build-time");
             Version.RESOURCES_VERSION = Version.VERSION.toLowerCase();
-            if (Version.RESOURCES_VERSION.endsWith("-snapshot")) {
-                Version.RESOURCES_VERSION = Version.RESOURCES_VERSION.replace("-snapshot", "-" + Time.currentTime());
-            }
         } catch (IOException e) {
-            Version.VERSION= Version.UNKNOWN;
-            Version.BUILD_TIME= Version.UNKNOWN;
+            Version.VERSION = Version.UNKNOWN;
+            Version.BUILD_TIME = Version.UNKNOWN;
         }
-
     }
 
 }
diff --git a/distribution/adapters/wildfly-adapter/wildfly-adapter-zip/assembly.xml b/distribution/adapters/wildfly-adapter/wildfly-adapter-zip/assembly.xml
index ea896e6..b5f6593 100755
--- a/distribution/adapters/wildfly-adapter/wildfly-adapter-zip/assembly.xml
+++ b/distribution/adapters/wildfly-adapter/wildfly-adapter-zip/assembly.xml
@@ -50,5 +50,9 @@
              <source>../../shared-cli/adapter-install.cli</source>
              <outputDirectory>bin</outputDirectory>
         </file>
+        <file>
+             <source>cli/adapter-install-offline.cli</source>
+             <outputDirectory>bin</outputDirectory>
+        </file>
     </files>
 </assembly>
diff --git a/distribution/adapters/wildfly-adapter/wildfly-adapter-zip/cli/adapter-install-offline.cli b/distribution/adapters/wildfly-adapter/wildfly-adapter-zip/cli/adapter-install-offline.cli
new file mode 100644
index 0000000..8b55142
--- /dev/null
+++ b/distribution/adapters/wildfly-adapter/wildfly-adapter-zip/cli/adapter-install-offline.cli
@@ -0,0 +1,5 @@
+embed-server --server-config=standalone.xml
+/subsystem=security/security-domain=keycloak/:add
+/subsystem=security/security-domain=keycloak/authentication=classic/:add(login-modules=[{ "code" => "org.keycloak.adapters.jboss.KeycloakLoginModule","flag" => "required"}])
+/extension=org.keycloak.keycloak-adapter-subsystem/:add(module=org.keycloak.keycloak-adapter-subsystem)
+/subsystem=keycloak:add
\ No newline at end of file
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/cache.xml b/docbook/auth-server-docs/reference/en/en-US/modules/cache.xml
index c808de7..4d68eb4 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/cache.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/cache.xml
@@ -25,61 +25,46 @@
     <section>
         <title>Disabling Caches</title>
         <para>
-            The realm and user caches can be disabled through configuration or through the management console.  To
-            manally disable the realm or user cache, you must edit the <literal>keycloak-server.json</literal> file
+            The realm and user caches can be cleared through the management console.  To
+            disable the realm or user cache, you must edit the <literal>keycloak-server.json</literal> file
             in your distribution.  Here's what the config looks like initially.
         </para>
         <para>
             <programlisting><![CDATA[
-    "realmCache": {
-        "provider": "${keycloak.realm.cache.provider:mem}"
+    "userCache": {
+        "infinispan" : {
+            "enabled": true
+        }
     },
 
-    "userCache": {
-        "provider": "${keycloak.user.cache.provider:mem}",
-        "mem": {
-            "maxSize": 20000
+    "realmCache": {
+        "infinispan" : {
+            "enabled": true
         }
     },
 ]]></programlisting>
         </para>
         <para>You must then change it to:
             <programlisting><![CDATA[
-    "realmCache": {
-        "provider": "${keycloak.realm.cache.provider:none}"
+    "userCache": {
+        "infinispan" : {
+            "enabled": false
+        }
     },
 
-    "userCache": {
-        "provider": "${keycloak.user.cache.provider:none}"
+    "realmCache": {
+        "infinispan" : {
+            "enabled": false
+        }
     },
 ]]></programlisting>
         </para>
-        <para>
-           You can also disable either of the caches at runtime through the Keycloak admin console Realm Settings page.
-           This will not permanently disable the cache.  If you reboot the server, the cache will be re-enabled unless
-            you manualy disable the cache in the <literal>keycloak-server.json</literal> file.
-        </para>
     </section>
     <section>
         <title>Clear Caches</title>
         <para>
             To clear the realm or user cache, go to the Keycloak admin console Realm Settings->Cache Config page.  Disable the cache
-            you want.  Save the settings.  Then re-enable the cache.  This will cause the cache to be cleared.
-        </para>
-    </section>
-    <section>
-        <title>Cache Config</title>
-        <para>
-            Cache configuration is done within <literal>keycloak-server.json</literal>.  Changes to this file will not
-            be seen by the server until you reboot.  Currently you can only configure the max size of the user cache.
-            <programlisting><![CDATA[
-    "userCache": {
-        "provider": "${keycloak.user.cache.provider:mem}",
-        "mem": {
-            "maxSize": 20000
-        }
-    },
-]]></programlisting>
+            you want. This will cause the cache to be cleared.
         </para>
     </section>
-</chapter>
\ No newline at end of file
+</chapter>
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/jboss-adapter.xml b/docbook/auth-server-docs/reference/en/en-US/modules/jboss-adapter.xml
index e9b9610..80abbcf 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/jboss-adapter.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/jboss-adapter.xml
@@ -27,7 +27,7 @@
     <section id="jboss-adapter-installation">
         <title>Adapter Installation</title>
     <para>
-        Adapters are no longer included with the appliance or war distribution.Each adapter is a separate download on
+        Adapters are no longer included with the appliance or war distribution. Each adapter is a separate download on
         the Keycloak download site.  They are also available as a maven artifact.
     </para>
     <para>
@@ -75,6 +75,13 @@ $ jboss-cli.sh -c --file=adapter-install.cli
         The script will add the extension, subsystem, and optional security-domain as described below.
     </para>
     <para>
+        For more recent versions of WildFly there's also a offline CLI script that can be used to install the adapter while the server is not running:
+<programlisting>
+$ cd $JBOSS_HOME/bin
+$ jboss-cli.sh -c --file=adapter-install-offline.cli
+</programlisting>
+    </para>
+    <para>
 <programlisting><![CDATA[
 <server xmlns="urn:jboss:domain:1.4">
 
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/saml.xml b/docbook/auth-server-docs/reference/en/en-US/modules/saml.xml
index ded8f64..d6c93fa 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/saml.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/saml.xml
@@ -18,16 +18,20 @@
 <chapter id="saml">
     <title>SAML SSO</title>
     <para>
-        Keycloak supports SAML 2.0 for registered applications.  Both POST and Redirect bindings are supported.  You can choose
-        to require client signature validation and can have the server sign and/or encrypt responses as well.  We do not
-        yet support logout via redirects.  All logouts happen via a background POST binding request to the application
-        that will be logged out.  We do not support SAML 1.1 either.  If you want support for either of those, please
+        Keycloak supports SAML 2.0 for registered applications. Both POST and Redirect bindings are supported. You can
+        choose
+        to require client signature validation and can have the server sign and/or encrypt responses as well. We do not
+        yet support logout via redirects. All logouts happen via a background POST binding request to the application
+        that will be logged out. We do not support SAML 1.1 either. If you want support for either of those, please
         log a JIRA request and we'll schedule it.
     </para>
     <para>
-        When you create an application in the admin console, you can choose which protocol the application will log in with.
-        In the application create screen, choose <literal>saml</literal> from the protocol list.  After that there
-        are a bunch of configuration options.  Here is a description of each item:
+        When you create an application in the admin console, you can choose which protocol the application will log in
+        with.
+        In the application create screen, choose
+        <literal>saml</literal>
+        from the protocol list. After that there
+        are a bunch of configuration options. Here is a description of each item:
     </para>
     <para>
         <variablelist>
@@ -35,7 +39,7 @@
                 <term>Client ID</term>
                 <listitem>
                     <para>
-                        This value must match the issuer value sent with AuthNRequests.  Keycloak will pull the issuer
+                        This value must match the issuer value sent with AuthNRequests. Keycloak will pull the issuer
                         from the Authn SAML request and match it to a client by this value.
                     </para>
                 </listitem>
@@ -45,7 +49,8 @@
                 <listitem>
                     <para>
                         SAML login responses may specify the authentication method used (password, etc.) as well as
-                        a timestamp of the login.  Setting this to on will include that statement in the response document.
+                        a timestamp of the login. Setting this to on will include that statement in the response
+                        document.
                     </para>
                 </listitem>
             </varlistentry>
@@ -53,7 +58,8 @@
                 <term>Multi-valued Roles</term>
                 <listitem>
                     <para>
-                        If this switch is off, any user role mappings will have a corresponding attribute created for it.
+                        If this switch is off, any user role mappings will have a corresponding attribute created for
+                        it.
                         If this switch is turn on, only one role attribute will be created, but it will have
                         multiple values within in.
                     </para>
@@ -71,7 +77,9 @@
                 <term>Sign Assertions</term>
                 <listitem>
                     <para>
-                        With the <literal>Sign Documents</literal> switch signs the whole document.  With this setting
+                        With the
+                        <literal>Sign Documents</literal>
+                        switch signs the whole document. With this setting
                         you just assign the assertions of the document.
                     </para>
                 </listitem>
@@ -88,7 +96,7 @@
                 <term>Encrypt Assertions</term>
                 <listitem>
                     <para>
-                        Encrypt assertions in SAML documents with the realm's private key.  The AES algorithm is used
+                        Encrypt assertions in SAML documents with the realm's private key. The AES algorithm is used
                         with a key size of 128 bits.
                     </para>
                 </listitem>
@@ -97,8 +105,10 @@
                 <term>Client Signature Required</term>
                 <listitem>
                     <para>
-                        Expect that documents coming from a client are signed.  Keycloak will validate this signature
-                        using the client keys set up in the <literal>Application Keys</literal> submenu item.
+                        Expect that documents coming from a client are signed. Keycloak will validate this signature
+                        using the client keys set up in the
+                        <literal>Application Keys</literal>
+                        submenu item.
                     </para>
                 </listitem>
             </varlistentry>
@@ -106,8 +116,10 @@
                 <term>Force POST Binding</term>
                 <listitem>
                     <para>
-                        By default, Keycloak will respond using the initial SAML binding of the original request.  By turning
-                        on this switch, you will force Keycloak to always respond using the SAML POST Binding even if the
+                        By default, Keycloak will respond using the initial SAML binding of the original request. By
+                        turning
+                        on this switch, you will force Keycloak to always respond using the SAML POST Binding even if
+                        the
                         original request was the Redirect binding.
                     </para>
                 </listitem>
@@ -116,9 +128,12 @@
                 <term>Front Channel Logout</term>
                 <listitem>
                     <para>
-                        If true, this application requires a browser redirect to be able to perform a logout.  For example,
-                        the application may require a cookie to be reset which could only be done by a done via a redirect.
-                        If this switch is false, then Keycloak will invoke a background SAML request to logout the application.
+                        If true, this application requires a browser redirect to be able to perform a logout. For
+                        example,
+                        the application may require a cookie to be reset which could only be done by a done via a
+                        redirect.
+                        If this switch is false, then Keycloak will invoke a background SAML request to logout the
+                        application.
                     </para>
                 </listitem>
             </varlistentry>
@@ -126,7 +141,8 @@
                 <term>Force Name ID Format</term>
                 <listitem>
                     <para>
-                        If the request has a name ID policy, ignore it and used the value configured in the admin console
+                        If the request has a name ID policy, ignore it and used the value configured in the admin
+                        console
                         under Name ID Format
                     </para>
                 </listitem>
@@ -135,18 +151,118 @@
                 <term>Name ID Format</term>
                 <listitem>
                     <para>
-                        Name ID Format for the subject.  If no name ID policy is specified in the request or if the
-                        Force Name ID Format attribute is true, this value is used.
+                        Name ID Format for the subject. If no name ID policy is specified in the request or if the
+                        Force Name ID Format attribute is true, this value is used. Properties used for each of the
+                        respective formats are defined below.
                     </para>
+                    <variablelist>
+                        <varlistentry>
+                            <term>username</term>
+                            <listitem>
+                                <para>
+                                    Format:
+                                    <literal>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</literal>
+                                </para>
+                                <para>
+                                    Source:
+                                    <literal>UserModel.userName</literal>
+                                    property
+                                </para>
+                            </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                            <term>email</term>
+                            <listitem>
+                                <para>
+                                    Format:
+                                    <literal>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</literal>
+                                </para>
+                                <para>
+                                    Source:
+                                    <literal>UserModel.email</literal>
+                                    property
+                                </para>
+                            </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                            <term>transient</term>
+                            <listitem>
+                                <para>
+                                    Format:
+                                    <literal>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</literal>
+                                </para>
+                                <para>
+                                    Source:<literal>G-$randomUuid</literal>, I.E.
+                                    <literal>G-5ef5b38f-864f-41ad-82a0-04ade9139500</literal>
+                                </para>
+                            </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                            <term>persistent</term>
+                            <listitem>
+                                <para>
+                                    Format:
+                                    <literal>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</literal>
+                                </para>
+                                <para>
+                                    The persistent identifier will be evaluated in the following order. If one step
+                                    does not yield a value, processing will continue on to the next until a value is
+                                    found.
+                                </para>
+                                <variablelist>
+                                    <varlistentry>
+                                        <term>
+                                            1) saml.persistent.name.id.for.$clientId
+                                            <literal>UserModel</literal> attribute
+                                        </term>
+                                        <listitem>
+                                            <para>
+                                                identifier unique to this client/user pair.
+                                            </para>
+                                        </listitem>
+                                    </varlistentry>
+                                    <varlistentry>
+                                        <term>
+                                            2) saml.persistent.name.id.for.*
+                                            <literal>UserModel</literal> attribute
+                                        </term>
+                                        <listitem>
+                                            <para>
+                                                user identifier for all clients, unless otherwise overridden by a
+                                                $clientId attribute.
+                                            </para>
+                                        </listitem>
+                                    </varlistentry>
+                                    <varlistentry>
+                                        <term>
+                                            3) G-$randomUuid
+                                        </term>
+                                        <listitem>
+                                            <para>
+                                                I.E.<literal>G-5ef5b38f-864f-41ad-82a0-04ade9139500</literal>. If
+                                                neither a $clientId or wildcard user attribute is found, a persistent
+                                                identifier will be generated for the given client. Note that once this
+                                                identifier has been generated, a user attribute with the key
+                                                <emphasis>saml.persistent.name.id.for.$clientId</emphasis>
+                                                will be persisted and used on all subsequent requests against the given
+                                                client.
+                                            </para>
+                                        </listitem>
+                                    </varlistentry>
+                                </variablelist>
+                            </listitem>
+                        </varlistentry>
+                    </variablelist>
                 </listitem>
             </varlistentry>
             <varlistentry>
                 <term>Master SAML Processing URL</term>
                 <listitem>
                     <para>
-                        This URL will be used for all SAML requests and responsed directed to the SP.  It will be used
-                        as the Assertion Consumer Service URL and the Single Logout Service URL.  If a login request
-                        contains the Assertion Consumer Service URL, that will take precedence, but this URL must be valided
+                        This URL will be used for all SAML requests and responsed directed to the SP. It will be used
+                        as the Assertion Consumer Service URL and the Single Logout Service URL. If a login request
+                        contains the Assertion Consumer Service URL, that will take precedence, but this URL must be
+                        valided
                         by a registered Valid Redirect URI pattern
                     </para>
                 </listitem>
@@ -186,9 +302,10 @@
         </variablelist>
     </para>
     <para>
-        For login to work, Keycloak needs to be able to resolve the URL for the Assertion Consumer Service of the SP.  If
-        you are relying on the SP to provide this URL in the login request, then you must register valid redirect uri patterns
-        so that this URL can be validated.  You can set the Master SAML Processing URL as well, or alternatively, you can
+        For login to work, Keycloak needs to be able to resolve the URL for the Assertion Consumer Service of the SP. If
+        you are relying on the SP to provide this URL in the login request, then you must register valid redirect uri
+        patterns
+        so that this URL can be validated. You can set the Master SAML Processing URL as well, or alternatively, you can
         specify the Assertion Consumer Service URL per binding.
     </para>
     <para>
@@ -196,32 +313,43 @@
         you want Keycloak to use.
     </para>
     <para>
-        One thing to note is that roles are not treated as a hierarchy.  So, any role mappings will just be added
-        to the role attributes in the SAML document using their basic name.  So, if you have multiple application roles
-        you might have name collisions.  You can use the Scope Mapping menu item to control which role mappings are set
+        One thing to note is that roles are not treated as a hierarchy. So, any role mappings will just be added
+        to the role attributes in the SAML document using their basic name. So, if you have multiple application roles
+        you might have name collisions. You can use the Scope Mapping menu item to control which role mappings are set
         in the response.
     </para>
     <section>
         <title>SAML Entity Descriptor</title>
         <para>
-            If you go into the admin console in the application list menu page you will see an <literal>Import</literal>
-            button.  If you click on that you can import SAML Service Provider definitions using the <ulink url="http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf">Entity Descriptor</ulink>
-            format described in SAML 2.0.  You should review all the information there to make sure everything is set up correctly.
+            If you go into the admin console in the application list menu page you will see an
+            <literal>Import</literal>
+            button. If you click on that you can import SAML Service Provider definitions using the
+            <ulink url="http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf">Entity Descriptor
+            </ulink>
+            format described in SAML 2.0. You should review all the information there to make sure everything is set up
+            correctly.
         </para>
         <para>
-            Each realm has a URL where you can view the XML entity descriptor for the IDP.  <literal>root/auth/realms/{realm}/protocol/saml/descriptor</literal>
+            Each realm has a URL where you can view the XML entity descriptor for the IDP.
+            <literal>root/auth/realms/{realm}/protocol/saml/descriptor</literal>
         </para>
     </section>
     <section>
         <title>IDP Initiated Login</title>
         <para>
-            IDP Initiated Login is a feature that where you can set up a URL on the Keycloak server that will log you into a specific application/client.  To set this up
-            go to the client page in the admin console of the client you want to set this up for.  Specify the <literal>IDP Initiated SSO URL Name</literal>.  This is a simple string
-            with no whitespace in it.  After this you can reference your client at the following URL:  <literal>root/auth/realms/{realm}/protocol/saml/clients/{url-name}</literal>
+            IDP Initiated Login is a feature that where you can set up a URL on the Keycloak server that will log you
+            into a specific application/client. To set this up
+            go to the client page in the admin console of the client you want to set this up for. Specify the<literal>
+            IDP Initiated SSO URL Name</literal>. This is a simple string
+            with no whitespace in it. After this you can reference your client at the following URL:
+            <literal>root/auth/realms/{realm}/protocol/saml/clients/{url-name}</literal>
         </para>
         <para>
-            If your client requires a special relay state, you can also configure this in the admin console.  Alternatively, you can specify the relay state in a
-            <literal>RelayState</literal> query parameter, i.e. :  <literal>root/auth/realms/{realm}/protocol/saml/clients/{url-name}?RelayState=thestate</literal>
+            If your client requires a special relay state, you can also configure this in the admin console.
+            Alternatively, you can specify the relay state in a
+            <literal>RelayState</literal>
+            query parameter, i.e. :
+            <literal>root/auth/realms/{realm}/protocol/saml/clients/{url-name}?RelayState=thestate</literal>
         </para>
     </section>
 </chapter>
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientTemplateAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientTemplateAdapter.java
index 211de4f..ce4095e 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientTemplateAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientTemplateAdapter.java
@@ -53,7 +53,7 @@ public class ClientTemplateAdapter implements ClientTemplateModel {
 
     private void getDelegateForUpdate() {
         if (updated == null) {
-            cacheSession.registerApplicationInvalidation(getId());
+            cacheSession.registerClientTemplateInvalidation(getId());
             updated = cacheSession.getDelegate().getClientTemplateById(getId(), cachedRealm);
             if (updated == null) throw new IllegalStateException("Not found in database");
         }

pom.xml 2(+1 -1)

diff --git a/pom.xml b/pom.xml
index dcc52c0..ec2b0e6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -43,7 +43,7 @@
         <apacheds.version>2.0.0-M17</apacheds.version>
         <apacheds.codec.version>1.0.0-M23</apacheds.codec.version>
         <org.apache.james.apache-mime4j.version>0.6</org.apache.james.apache-mime4j.version>
-        <bouncycastle.crypto.version>1.50</bouncycastle.crypto.version>
+        <bouncycastle.crypto.version>1.52</bouncycastle.crypto.version>
         <jackson.version>2.5.4</jackson.version>
         <apache.httpcomponents.version>4.3.6</apache.httpcomponents.version>
         <apache.httpcomponents.httpcore.version>4.3.3</apache.httpcomponents.httpcore.version>
diff --git a/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java b/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java
index d5d2ccb..bfbf7a1 100755
--- a/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java
+++ b/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java
@@ -98,7 +98,7 @@ public abstract class AuthorizationEndpointBase {
 
         List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
         for (IdentityProviderModel identityProvider : identityProviders) {
-            if (identityProvider.isAuthenticateByDefault()) {
+            if (identityProvider.isEnabled() && identityProvider.isAuthenticateByDefault()) {
                 // TODO if we are isPassive we should propagate this flag to default identity provider also if possible
                 return buildRedirectToIdentityProvider(identityProvider.getAlias(), new ClientSessionCode(realm, clientSession).getCode());
             }
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
index 2d8f756..042779f 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
@@ -281,16 +281,7 @@ public class SamlProtocol implements LoginProtocol {
             // "G-" stands for "generated" Add this for the slight possibility of collisions.
             return "G-" + UUID.randomUUID().toString();
         } else if (nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get())) {
-            // generate a persistent user id specifically for each client.
-            UserModel user = userSession.getUser();
-            String name = SAML_PERSISTENT_NAME_ID_FOR + "." + clientSession.getClient().getClientId();
-            String samlPersistentId = user.getFirstAttribute(name);
-            if (samlPersistentId != null)
-                return samlPersistentId;
-            // "G-" stands for "generated"
-            samlPersistentId = "G-" + UUID.randomUUID().toString();
-            user.setSingleAttribute(name, samlPersistentId);
-            return samlPersistentId;
+            return getPersistentNameId(clientSession, userSession);
         } else if (nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get())) {
             // TODO: Support for persistent NameID (pseudo-random identifier persisted in user object)
             return userSession.getUser().getUsername();
@@ -299,6 +290,43 @@ public class SamlProtocol implements LoginProtocol {
         }
     }
 
+    /**
+     * Attempts to retrieve the persistent type NameId as follows:
+     *
+     * <ol>
+     *     <li>saml.persistent.name.id.for.$clientId user attribute</li>
+     *     <li>saml.persistent.name.id.for.* user attribute</li>
+     *     <li>G-$randomUuid</li>
+     * </ol>
+     *
+     * If a randomUuid is generated, an attribute for the given saml.persistent.name.id.for.$clientId will be generated,
+     * otherwise no state change will occur with respect to the user's attributes.
+     *
+     * @return the user's persistent NameId
+     */
+    protected String getPersistentNameId(final ClientSessionModel clientSession, final UserSessionModel userSession) {
+        // attempt to retrieve the UserID for the client-specific attribute
+        final UserModel user = userSession.getUser();
+        final String clientNameId = String.format("%s.%s", SAML_PERSISTENT_NAME_ID_FOR,
+                clientSession.getClient().getClientId());
+        String samlPersistentNameId = user.getFirstAttribute(clientNameId);
+        if (samlPersistentNameId != null) {
+            return samlPersistentNameId;
+        }
+
+        // check for a wildcard attribute
+        final String wildcardNameId = String.format("%s.*", SAML_PERSISTENT_NAME_ID_FOR);
+        samlPersistentNameId = user.getFirstAttribute(wildcardNameId);
+        if (samlPersistentNameId != null) {
+            return samlPersistentNameId;
+        }
+
+        // default to generated.  "G-" stands for "generated"
+        samlPersistentNameId = "G-" + UUID.randomUUID().toString();
+        user.setSingleAttribute(clientNameId, samlPersistentNameId);
+        return samlPersistentNameId;
+    }
+
     @Override
     public Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode) {
         ClientSessionModel clientSession = accessCode.getClientSession();
diff --git a/testsuite/integration-arquillian/servers/pom.xml b/testsuite/integration-arquillian/servers/pom.xml
index 070dfb6..a4a3c81 100644
--- a/testsuite/integration-arquillian/servers/pom.xml
+++ b/testsuite/integration-arquillian/servers/pom.xml
@@ -41,6 +41,14 @@
             </modules>
         </profile>
         <profile>
+            <id>auth-server-wildfly-cluster</id>
+            <modules>
+                <module>wildfly</module>
+                <module>wildfly-balancer</module>
+            </modules>
+        </profile>
+        
+        <profile>
             <id>auth-server-eap7</id>
             <modules>
                 <module>eap7</module>
diff --git a/testsuite/integration-arquillian/servers/wildfly/pom.xml b/testsuite/integration-arquillian/servers/wildfly/pom.xml
index a39738a..87f8674 100644
--- a/testsuite/integration-arquillian/servers/wildfly/pom.xml
+++ b/testsuite/integration-arquillian/servers/wildfly/pom.xml
@@ -1,20 +1,20 @@
 <?xml version="1.0"?>
 <!--
-  ~ Copyright 2016 Red Hat, Inc. and/or its affiliates
-  ~ and other contributors as indicated by the @author tags.
-  ~
-  ~ 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.
-  -->
+~ Copyright 2016 Red Hat, Inc. and/or its affiliates
+~ and other contributors as indicated by the @author tags.
+~
+~ 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.
+-->
 
 <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
@@ -254,16 +254,16 @@
                                     <goal>copy-resources</goal>
                                 </goals>
                                 <configuration>
-                                     <outputDirectory>${keycloak.server.home}/standalone/configuration</outputDirectory>
-                                     <resources>
-                                         <resource>
-                                              <directory>src/main/keystore</directory>
-                                              <includes>
-                                                  <include>keycloak.jks</include>
-                                                  <include>keycloak.truststore</include>
-                                              </includes>
-                                         </resource>
-                                     </resources>
+                                    <outputDirectory>${keycloak.server.home}/standalone/configuration</outputDirectory>
+                                    <resources>
+                                        <resource>
+                                            <directory>src/main/keystore</directory>
+                                            <includes>
+                                                <include>keycloak.jks</include>
+                                                <include>keycloak.truststore</include>
+                                            </includes>
+                                        </resource>
+                                    </resources>
                                 </configuration>
                             </execution>
                         </executions>
@@ -412,5 +412,50 @@
                 </plugins>
             </build>
         </profile>        
+        
+        <profile>
+            <id>auth-server-wildfly-cluster</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>xml-maven-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>configure-wildfly-datasource</id>
+                                <phase>process-resources</phase>
+                                <goals>
+                                    <goal>transform</goal>
+                                </goals>
+                                <configuration>
+                                    <transformationSets>
+                                        <!-- point KeycloakDS datasource to H2 running on TCP port -->
+                                        <transformationSet>
+                                            <dir>${keycloak.server.home}/standalone/configuration</dir>
+                                            <includes>
+                                                <include>standalone-ha.xml</include>
+                                            </includes>
+                                            <stylesheet>src/main/xslt/datasource-jdbc-url.xsl</stylesheet>
+                                            <outputDir>${keycloak.server.home}/standalone/configuration</outputDir>
+                                            <parameters>
+                                                <parameter>
+                                                    <name>pool.name</name>
+                                                    <value>KeycloakDS</value>
+                                                </parameter>
+                                                <parameter>
+                                                    <name>jdbc.url</name>
+                                                    <value>jdbc:h2:tcp://${jboss.bind.address:localhost}:9092/mem:keycloak;DB_CLOSE_DELAY=-1</value>
+                                                </parameter>
+                                            </parameters>
+                                        </transformationSet>
+                                    </transformationSets>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+        
     </profiles>
 </project>
diff --git a/testsuite/integration-arquillian/servers/wildfly/src/main/xslt/datasource-jdbc-url.xsl b/testsuite/integration-arquillian/servers/wildfly/src/main/xslt/datasource-jdbc-url.xsl
new file mode 100644
index 0000000..589ee4c
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/wildfly/src/main/xslt/datasource-jdbc-url.xsl
@@ -0,0 +1,36 @@
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                xmlns:xalan="http://xml.apache.org/xalan"
+                xmlns:j="urn:jboss:domain:4.0"
+                xmlns:ds="urn:jboss:domain:datasources:4.0"
+                xmlns:k="urn:jboss:domain:keycloak:1.1"
+                xmlns:sec="urn:jboss:domain:security:1.2"
+                version="2.0"
+                exclude-result-prefixes="xalan j ds k sec">
+
+    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" xalan:indent-amount="4" standalone="no"/>
+    <xsl:strip-space elements="*"/>
+
+
+    <xsl:variable name="nsDS" select="'urn:jboss:domain:datasources:'"/>
+    
+    <xsl:param name="pool.name" select="'KeycloakDS'"/>
+    <xsl:param name="jdbc.url" />
+
+    <!-- replace JDBC URL -->
+    <xsl:template match="//*[local-name()='subsystem' and starts-with(namespace-uri(), $nsDS)]
+		         /*[local-name()='datasources' and starts-with(namespace-uri(), $nsDS)]
+                         /*[local-name()='datasource' and starts-with(namespace-uri(), $nsDS) and @pool-name=$pool.name]
+                         /*[local-name()='connection-url' and starts-with(namespace-uri(), $nsDS)]">
+        <connection-url>
+            <xsl:value-of select="$jdbc.url"/>
+        </connection-url>
+    </xsl:template>
+
+    <!-- Copy everything else. -->
+    <xsl:template match="@*|node()">
+        <xsl:copy>
+            <xsl:apply-templates select="@*|node()" />
+        </xsl:copy>
+    </xsl:template>
+
+</xsl:stylesheet>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/servers/wildfly-balancer/assembly.xml b/testsuite/integration-arquillian/servers/wildfly-balancer/assembly.xml
new file mode 100644
index 0000000..a3e36ae
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/wildfly-balancer/assembly.xml
@@ -0,0 +1,46 @@
+<!--
+  ~ Copyright 2016 Red Hat, Inc. and/or its affiliates
+  ~ and other contributors as indicated by the @author tags.
+  ~
+  ~ 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.
+  -->
+
+<assembly>
+    
+    <id>wildfly-balancer</id>
+
+    <formats>
+        <format>zip</format>
+    </formats>
+
+    <includeBaseDirectory>false</includeBaseDirectory>
+
+    <fileSets>
+        <fileSet>
+            <directory>${wildfly.balancer.home}</directory>
+            <outputDirectory>wildfly-balancer-${project.version}</outputDirectory>
+            <excludes>
+                <exclude>**/*.sh</exclude>
+            </excludes>
+        </fileSet>
+        <fileSet>
+            <directory>${wildfly.balancer.home}</directory>
+            <outputDirectory>wildfly-balancer-${project.version}</outputDirectory>
+            <includes>
+                <include>**/*.sh</include>
+            </includes>
+            <fileMode>0755</fileMode>
+        </fileSet>
+    </fileSets>
+
+</assembly>
diff --git a/testsuite/integration-arquillian/servers/wildfly-balancer/pom.xml b/testsuite/integration-arquillian/servers/wildfly-balancer/pom.xml
new file mode 100644
index 0000000..895be72
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/wildfly-balancer/pom.xml
@@ -0,0 +1,118 @@
+<?xml version="1.0"?>
+<!--
+~ Copyright 2016 Red Hat, Inc. and/or its affiliates
+~ and other contributors as indicated by the @author tags.
+~
+~ 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.
+-->
+
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+    <parent>
+        <groupId>org.keycloak.testsuite</groupId>
+        <artifactId>integration-arquillian-servers</artifactId>
+        <version>1.9.0.Final-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>integration-arquillian-server-wildfly-balancer</artifactId>
+    <packaging>pom</packaging>
+    <name>Wildfly Load Balancer</name>
+    
+    <properties>
+        <wildfly.balancer.home>${project.build.directory}/unpacked/wildfly-${wildfly.version}</wildfly.balancer.home>
+    </properties>
+    
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-deploy-plugin</artifactId>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+            </plugin>            
+            
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>unpack-wildfly</id>
+                        <phase>generate-resources</phase>
+                        <goals>
+                            <goal>unpack</goal>
+                        </goals>
+                        <configuration>
+                            <artifactItems>
+                                <artifactItem>
+                                    <groupId>org.wildfly</groupId>
+                                    <artifactId>wildfly-dist</artifactId>
+                                    <version>${wildfly.version}</version>
+                                    <type>zip</type>
+                                    <outputDirectory>${project.build.directory}/unpacked</outputDirectory>
+                                </artifactItem>
+                            </artifactItems>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>xml-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>configure-mod-cluster</id>
+                        <phase>process-resources</phase>
+                        <goals>
+                            <goal>transform</goal>
+                        </goals>
+                        <configuration>
+                            <transformationSets>
+                                <transformationSet>
+                                    <dir>${wildfly.balancer.home}/standalone/configuration</dir>
+                                    <includes>
+                                        <include>standalone.xml</include>
+                                    </includes>
+                                    <stylesheet>src/main/xslt/mod_cluster.xsl</stylesheet>
+                                    <outputDir>${wildfly.balancer.home}/standalone/configuration</outputDir>
+                                </transformationSet>
+                            </transformationSets>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+           
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>create-zip</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                        <configuration>
+                            <descriptors>
+                                <descriptor>assembly.xml</descriptor>
+                            </descriptors>
+                            <appendAssemblyId>false</appendAssemblyId>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+    
+</project>
diff --git a/testsuite/integration-arquillian/servers/wildfly-balancer/src/main/xslt/mod_cluster.xsl b/testsuite/integration-arquillian/servers/wildfly-balancer/src/main/xslt/mod_cluster.xsl
new file mode 100644
index 0000000..9cb3774
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/wildfly-balancer/src/main/xslt/mod_cluster.xsl
@@ -0,0 +1,73 @@
+<!--
+~ Copyright 2016 Red Hat, Inc. and/or its affiliates
+~ and other contributors as indicated by the @author tags.
+~
+~ 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.
+-->
+
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                xmlns:xalan="http://xml.apache.org/xalan"
+                xmlns:s="urn:jboss:domain:4.0"
+                xmlns:u="urn:jboss:domain:undertow:3.0"
+                version="2.0"
+                exclude-result-prefixes="xalan j u">
+
+    <xsl:param name="config"/>
+
+    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" xalan:indent-amount="4" standalone="no"/>
+    <xsl:strip-space elements="*"/>
+
+    <!--enable mod_cluster extension-->
+    <xsl:template match="//s:extensions">
+        <xsl:copy>
+            <xsl:apply-templates select="node()|@*"/>
+            <extension module="org.jboss.as.modcluster"/>
+        </xsl:copy>
+    </xsl:template>
+
+    <!--add filter-ref-->
+    <xsl:template match="//u:server[@name='default-server']/u:host[@name='default-host']">
+        <xsl:copy>
+            <xsl:apply-templates select="node()|@*"/>
+            <filter-ref name="modcluster"/>
+        </xsl:copy>
+    </xsl:template>
+    
+    <!--add filter-->
+    <xsl:template match="//u:filters">
+        <xsl:copy>
+            <xsl:apply-templates select="node()|@*"/>
+            <mod-cluster 
+                name="modcluster" 
+                advertise-socket-binding="modcluster" 
+                management-socket-binding="http"
+                enable-http2="true"
+            />
+        </xsl:copy>
+    </xsl:template>
+
+    <!--add socket binding-->
+    <xsl:template match="//s:socket-binding-group[@name='standard-sockets']">
+        <xsl:copy>
+            <xsl:apply-templates select="node()|@*"/>
+            <socket-binding name="modcluster" port="23364" multicast-address="224.0.1.105"/>
+        </xsl:copy>
+    </xsl:template>
+
+    <xsl:template match="@*|node()">
+        <xsl:copy>
+            <xsl:apply-templates select="@*|node()" />
+        </xsl:copy>
+    </xsl:template>
+
+</xsl:stylesheet>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/pom.xml b/testsuite/integration-arquillian/tests/base/pom.xml
index e08b2ad..cc84be1 100644
--- a/testsuite/integration-arquillian/tests/base/pom.xml
+++ b/testsuite/integration-arquillian/tests/base/pom.xml
@@ -35,7 +35,10 @@
         <exclude.console>-</exclude.console>
         <exclude.account>-</exclude.account>
         <exclude.client>-</exclude.client>
-        <exclude.migration>-</exclude.migration>
+        <!--exclude migration tests by default, enabled by 'migration' profile in tests/pom.xml-->
+        <exclude.migration>**/migration/**/*Test.java</exclude.migration>
+        <!--exclude cluster tests by default, enabled by 'auth-server-*-cluster' profiles in tests/pom.xml-->
+        <exclude.cluster>**/cluster/**/*Test.java</exclude.cluster>
     </properties>
     
     <dependencies>
@@ -75,6 +78,7 @@
                         <exclude>${exclude.account}</exclude>
                         <exclude>${exclude.client}</exclude>
                         <exclude>${exclude.migration}</exclude>
+                        <exclude>${exclude.cluster}</exclude>
                     </excludes>
                 </configuration>
             </plugin>
@@ -108,29 +112,4 @@
 
     </build>
     
-    <profiles>    
-        <profile>
-            <id>no-account</id>
-            <properties>
-                <!-- Exclude all account management tests. -->
-                <exclude.account>**/account/**/*Test.java</exclude.account>
-            </properties>
-        </profile>
-        <profile>
-            <id>no-client</id>
-            <properties>
-                <!-- Exclude all client tests. -->
-                <exclude.client>**/client/**/*Test.java</exclude.client>
-            </properties>
-        </profile>
-        <profile>
-            <id>adapters-only</id>
-            <properties>
-                <exclude.account>**/account/**/*Test.java</exclude.account>
-                <exclude.client>**/client/**/*Test.java</exclude.client>
-                <exclude.migration>**/migration/**/*Test.java</exclude.migration>
-            </properties>
-        </profile>
-    </profiles>
-    
 </project>
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AppServerTestEnricher.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AppServerTestEnricher.java
index 9289593..e8703b4 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AppServerTestEnricher.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AppServerTestEnricher.java
@@ -40,9 +40,10 @@ public class AppServerTestEnricher {
         String appServerQ = (annotatedClass == null ? null
                 : annotatedClass.getAnnotation(AppServerContainer.class).value());
 
-        return appServerQ == null || appServerQ.isEmpty()
-                ? getAuthServerQualifier() // app server == auth server
-                : appServerQ;
+        return annotatedClass == null ? null // no @AppServerContainer annotation --> no adapter test
+                : (appServerQ == null || appServerQ.isEmpty() // @AppServerContainer annotation present but qualifier not set --> relative adapter test
+                        ? getAuthServerQualifier() // app server == auth server
+                        : appServerQ);
     }
 
     public static String getAppServerContextRoot() {
@@ -129,7 +130,7 @@ public class AppServerTestEnricher {
             String jbossHomePath = appServerInfo.getProperties().get("jbossHome");
 
             File bin = new File(jbossHomePath + "/bin");
-            
+
             File clientJar = new File(jbossHomePath + "/bin/client/jboss-cli-client.jar");
             if (!clientJar.exists()) {
                 clientJar = new File(jbossHomePath + "/bin/client/jboss-client.jar"); // AS7
@@ -137,7 +138,7 @@ public class AppServerTestEnricher {
             if (!clientJar.exists()) {
                 throw new IOException("JBoss CLI client JAR not found.");
             }
-            
+
             String command = "java -jar " + clientJar.getAbsolutePath();
             String adapterScript = "adapter-install.cli";
             String samlAdapterScript = "adapter-install-saml.cli";
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java
index 5000e3b..9cbfce6 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java
@@ -110,7 +110,10 @@ public class AuthServerTestEnricher {
 
             boolean authServerCluster = authServerQualifier.endsWith("-cluster");
 
-            String authServerType = authServerQualifier.replaceAll("^auth-server-", "").replaceAll("-cluster$", "");
+            String authServerType = authServerQualifier.replaceAll("auth-server-", "").replaceAll("-cluster", "");
+
+            log.info("authServerType:" + authServerType);
+
             String authServerFrontend = authServerCluster
                     ? "auth-server-" + authServerType + "-balancer" // in cluster mode the load-balancer container serves as auth server frontend
                     : authServerQualifier; // single-node mode
@@ -133,7 +136,7 @@ public class AuthServerTestEnricher {
             if (suiteContext.getAuthServerInfo() == null) {
                 throw new RuntimeException(String.format("No auth server activated. A container matching '%s' needs to be enabled in arquillian.xml.", authServerFrontend));
             }
-            if (authServerCluster && !suiteContext.getAuthServerBackendsInfo().isEmpty()) {
+            if (authServerCluster && suiteContext.getAuthServerBackendsInfo().isEmpty()) {
                 throw new RuntimeException(String.format("No cluster backend nodes activated. Containers matching '%sN' need to be enabled in arquillian.xml.", authServerBackend));
             }
 
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/h2/H2TestEnricher.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/h2/H2TestEnricher.java
new file mode 100644
index 0000000..705cbad
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/h2/H2TestEnricher.java
@@ -0,0 +1,40 @@
+package org.keycloak.testsuite.arquillian.h2;
+
+import java.sql.SQLException;
+import org.jboss.arquillian.core.api.annotation.Observes;
+import org.jboss.arquillian.test.spi.event.suite.AfterSuite;
+import org.jboss.arquillian.test.spi.event.suite.BeforeSuite;
+import org.jboss.logging.Logger;
+import org.h2.tools.Server;
+
+/**
+ * Starts H2 before suite and stops it after.
+ *
+ * @author tkyjovsk
+ */
+public class H2TestEnricher {
+
+    protected final Logger log = Logger.getLogger(this.getClass());
+
+    boolean runH2 = Boolean.parseBoolean(System.getProperty("run.h2", "false"));
+
+    private Server server = null;
+
+    public void startH2(@Observes(precedence = 2) BeforeSuite event) throws SQLException {
+        if (runH2) {
+            log.info("Starting H2 database.");
+            server = Server.createTcpServer();
+            server.start();
+            log.info(String.format("URL: %s", server.getURL()));
+        }
+    }
+
+    public void stopH2(@Observes(precedence = -2) AfterSuite event) {
+        if (runH2 && server.isRunning(false)) {
+            log.info("Stopping H2 database.");
+            server.stop();
+            assert !server.isRunning(false);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakArquillianExtension.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakArquillianExtension.java
index 7154b8a..275974f 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakArquillianExtension.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakArquillianExtension.java
@@ -26,6 +26,7 @@ import org.jboss.arquillian.core.spi.LoadableExtension;
 import org.jboss.arquillian.graphene.location.CustomizableURLResourceProvider;
 import org.jboss.arquillian.test.spi.enricher.resource.ResourceProvider;
 import org.jboss.arquillian.test.spi.execution.TestExecutionDecider;
+import org.keycloak.testsuite.arquillian.h2.H2TestEnricher;
 import org.keycloak.testsuite.arquillian.migration.MigrationTestExecutionDecider;
 import org.keycloak.testsuite.arquillian.undertow.CustomUndertowContainer;
 
@@ -48,7 +49,8 @@ public class KeycloakArquillianExtension implements LoadableExtension {
                 .service(DeploymentScenarioGenerator.class, DeploymentTargetModifier.class)
                 .service(ApplicationArchiveProcessor.class, DeploymentArchiveProcessor.class)
                 .observer(AuthServerTestEnricher.class)
-                .observer(AppServerTestEnricher.class);
+                .observer(AppServerTestEnricher.class)
+                .observer(H2TestEnricher.class);
 
         builder
                 .service(DeployableContainer.class, CustomUndertowContainer.class);
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SuiteContext.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SuiteContext.java
index c4bd23b..929c23d 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SuiteContext.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SuiteContext.java
@@ -93,9 +93,13 @@ public final class SuiteContext {
 
     @Override
     public String toString() {
+        String containers = "Auth server: " + (isAuthServerCluster() ? "\nFrontend: " : "")
+                + authServerInfo.getQualifier() + "\n";
+        for (ContainerInfo bInfo : getAuthServerBackendsInfo()) {
+            containers += "Backend: " + bInfo + "\n";
+        }
         return "SUITE CONTEXT:\n"
-                + "Auth server: " + authServerInfo.getQualifier() + "\n"
-                +(isAuthServerCluster() ? "Auth server cluster: " + getAuthServerBackendsInfo().size() + " nodes+\n" : "");
+                + containers;
     }
 
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/AbstractClusterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/AbstractClusterTest.java
new file mode 100644
index 0000000..9dd6089
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/AbstractClusterTest.java
@@ -0,0 +1,59 @@
+package org.keycloak.testsuite.cluster;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.jboss.arquillian.container.test.api.ContainerController;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import static org.junit.Assert.assertTrue;
+import org.keycloak.admin.client.Keycloak;
+import org.keycloak.models.Constants;
+import org.keycloak.testsuite.AbstractKeycloakTest;
+import org.keycloak.testsuite.arquillian.ContainerInfo;
+import static org.keycloak.testsuite.auth.page.AuthRealm.ADMIN;
+import static org.keycloak.testsuite.auth.page.AuthRealm.MASTER;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+public abstract class AbstractClusterTest extends AbstractKeycloakTest {
+
+    @ArquillianResource
+    protected ContainerController controller;
+
+    protected List<Keycloak> backendAdminClients = new ArrayList<>();
+
+    public void startBackendNodes(int count) {
+        if (count < 0 || count > 10) {
+            throw new IllegalArgumentException();
+        }
+        assertTrue(suiteContext.getAuthServerBackendsInfo().size() >= count);
+        for (int i = 0; i < count; i++) {
+
+            ContainerInfo backendNode = suiteContext.getAuthServerBackendsInfo().get(i);
+
+            controller.start(backendNode.getQualifier());
+            assertTrue(controller.isStarted(backendNode.getQualifier()));
+
+            log.info("Initializing admin client for: '" + backendNode.getContextRoot() + "/auth'");
+            backendAdminClients.add(Keycloak.getInstance(backendNode.getContextRoot() + "/auth",
+                    MASTER, ADMIN, ADMIN, Constants.ADMIN_CLI_CLIENT_ID));
+        }
+    }
+
+    protected ContainerInfo backendInfo(int i) {
+        return suiteContext.getAuthServerBackendsInfo().get(i);
+    }
+
+    protected void startBackendNode(int i) {
+        String container = backendInfo(i).getQualifier();
+        if (!controller.isStarted(container)) {
+            controller.start(container);
+        }
+    }
+
+    protected void stopBackendNode(int i) {
+        controller.kill(backendInfo(i).getQualifier());
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/TwoNodeClusterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/TwoNodeClusterTest.java
new file mode 100644
index 0000000..0984124
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/TwoNodeClusterTest.java
@@ -0,0 +1,131 @@
+package org.keycloak.testsuite.cluster;
+
+import java.util.List;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.admin.client.Keycloak;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.arquillian.ContainerInfo;
+import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
+import static org.keycloak.testsuite.util.WaitUtils.pause;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+public class TwoNodeClusterTest extends AbstractClusterTest {
+
+    @Override
+    public void addTestRealms(List<RealmRepresentation> testRealms) {
+    }
+
+    @Before
+    public void beforeTwoNodeClusterTest() {
+        startBackendNodes(2);
+        pause(3000);
+    }
+
+    @Test
+    public void testRealm() {
+        testRealm(TEST, false);
+    }
+
+    @Test
+    public void testRealmWithFailover() {
+        testRealm(TEST + "_fo", true);
+    }
+
+    public void testRealm(String realm, boolean containerFailover) {
+        RealmRepresentation testRealm = new RealmRepresentation();
+        testRealm.setRealm(realm);
+        testRealm.setEnabled(true);
+
+        // CREATE on node1
+        log.info("Creating test realm via node1.");
+        backend1AdminClient().realms().create(testRealm);
+        log.info("Test realm created.");
+
+        // check if created on node1
+        RealmRepresentation testRealmOnBackend1 = backend1AdminClient().realms().realm(realm).toRepresentation();
+        assertEquals(testRealmOnBackend1.getRealm(), testRealm.getRealm());
+        if (containerFailover) {
+            stopBackend1();
+        }
+
+        // check if created on node2
+        RealmRepresentation testRealmOnBackend2 = backend2AdminClient().realms().realm(realm).toRepresentation();
+        assertEquals(testRealmOnBackend1.getId(), testRealmOnBackend2.getId());
+        assertEquals(testRealmOnBackend1.getRealm(), testRealmOnBackend2.getRealm());
+
+        failback();
+
+        // UPDATE on node2
+        testRealmOnBackend2.setRealm(realm + "_updated");
+        backend2AdminClient().realms().realm(realm).update(testRealmOnBackend2);
+        if (containerFailover) {
+            stopBackend2();
+        }
+        // check if updated on node1
+        testRealmOnBackend1 = backend1AdminClient().realms().realm(realm).toRepresentation();
+        assertEquals(testRealmOnBackend1.getId(), testRealmOnBackend2.getId());
+        assertEquals(testRealmOnBackend1.getRealm(), testRealmOnBackend2.getRealm());
+
+        failback();
+
+        // DELETE on node1
+        backend1AdminClient().realms().realm(realm).remove();
+        if (containerFailover) {
+            stopBackend1();
+        }
+        // check if deleted on node2
+        boolean testRealmOnBackend2Exists = false;
+        for (RealmRepresentation realmOnBackend2 : backend2AdminClient().realms().findAll()) {
+            if (realm.equals(realmOnBackend2.getRealm())
+                    || testRealmOnBackend1.getId().equals(realmOnBackend2.getId())) {
+                testRealmOnBackend2Exists = true;
+                break;
+            }
+        }
+        assertFalse(testRealmOnBackend2Exists);
+    }
+
+    protected ContainerInfo backend1Info() {
+        return backendInfo(0);
+    }
+
+    protected ContainerInfo backend2Info() {
+        return backendInfo(1);
+    }
+
+    protected Keycloak backend1AdminClient() {
+        return backendAdminClients.get(0);
+    }
+
+    protected Keycloak backend2AdminClient() {
+        return backendAdminClients.get(1);
+    }
+
+    protected void startBackend1() {
+        startBackendNode(0);
+    }
+
+    protected void startBackend2() {
+        startBackendNode(1);
+    }
+
+    protected void failback() {
+        startBackend1();
+        startBackend2();
+    }
+
+    protected void stopBackend1() {
+        stopBackendNode(0);
+    }
+
+    protected void stopBackend2() {
+        stopBackendNode(1);
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
index 6c13daa..404f5b5 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
@@ -71,7 +71,7 @@
             <configuration>
                 <property name="enabled">${auth.server.wildfly.cluster}</property>
                 <property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
-                <property name="jbossHome">${wildfly.home}</property>
+                <property name="jbossHome">${keycloak.balancer.home}</property>
                 <property name="javaVmArguments">
                     -Djboss.socket.binding.port-offset=${auth.server.port.offset} 
                     -Xms64m -Xmx512m -XX:MaxPermSize=256m 
@@ -86,15 +86,35 @@
                 <property name="enabled">${auth.server.wildfly.cluster}</property>
                 <property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
                 <property name="jbossHome">${keycloak.backend1.home}</property>
+                <property name="serverConfig">standalone-ha.xml</property>
                 <property name="javaVmArguments">
                     -Djboss.socket.binding.port-offset=${auth.server.backend1.port.offset} 
                     -Xms64m -Xmx512m -XX:MaxPermSize=256m 
                     ${adapter.test.props}
+                    -Djboss.node.name=node1
                 </property>
+                <!--<property name="outputToConsole">false</property>-->
                 <property name="managementPort">${auth.server.backend1.management.port}</property>
                 <property name="startupTimeoutInSeconds">${startup.timeout.sec}</property>
             </configuration>
         </container>
+        <container qualifier="auth-server-wildfly-backend2" mode="manual" >
+            <configuration>
+                <property name="enabled">${auth.server.wildfly.cluster}</property>
+                <property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
+                <property name="jbossHome">${keycloak.backend2.home}</property>
+                <property name="serverConfig">standalone-ha.xml</property>
+                <property name="javaVmArguments">
+                    -Djboss.socket.binding.port-offset=${auth.server.backend2.port.offset} 
+                    -Xms64m -Xmx512m -XX:MaxPermSize=256m 
+                    ${adapter.test.props}
+                    -Djboss.node.name=node2
+                </property>
+                <!--<property name="outputToConsole">false</property>-->
+                <property name="managementPort">${auth.server.backend2.management.port}</property>
+                <property name="startupTimeoutInSeconds">${startup.timeout.sec}</property>
+            </configuration>
+        </container>
     </group>
     
     <container qualifier="auth-server-eap7" mode="suite" >
diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml
index 06511a2..4e35234 100644
--- a/testsuite/integration-arquillian/tests/pom.xml
+++ b/testsuite/integration-arquillian/tests/pom.xml
@@ -159,7 +159,27 @@
     </build>
 
     <profiles>
-        
+
+        <profile>
+            <id>no-account</id>
+            <properties>
+                <exclude.account>**/account/**/*Test.java</exclude.account>
+            </properties>
+        </profile>
+        <profile>
+            <id>no-client</id>
+            <properties>
+                <exclude.client>**/client/**/*Test.java</exclude.client>
+            </properties>
+        </profile>
+        <profile>
+            <id>no-base</id>
+            <properties>
+                <exclude.account>**/account/**/*Test.java</exclude.account>
+                <exclude.client>**/client/**/*Test.java</exclude.client>
+            </properties>
+        </profile>
+                
         <profile>
             <id>common-test-dependencies</id>
             <activation>
@@ -348,6 +368,7 @@
                 <dependency>
                     <groupId>com.h2database</groupId>
                     <artifactId>h2</artifactId>
+                    <scope>compile</scope>
                 </dependency>
                 <dependency>
                     <groupId>org.hibernate</groupId>
@@ -398,6 +419,7 @@
                 <adapter.test.props/>
                 <keycloak.home>${containers.home}/keycloak-${project.version}</keycloak.home>
                 <jboss.server.config.dir>${keycloak.home}/standalone/configuration</jboss.server.config.dir>
+                <h2.version>1.3.173</h2.version>
             </properties>
             <dependencies>
                 <dependency>
@@ -450,6 +472,111 @@
             </build>
         </profile>
         
+        
+        <profile>
+            <id>auth-server-wildfly-cluster</id>
+            <properties>
+                <!--disable exclusion pattern for cluster test which is enabled by default in base/pom.xml-->
+                <exclude.cluster>-</exclude.cluster>
+                
+                <auth.server.container>auth-server-wildfly-cluster</auth.server.container>
+                <startup.timeout.sec>300</startup.timeout.sec>
+                <adapter.test.props/>
+                <h2.version>1.3.173</h2.version>
+
+                <keycloak.balancer.home>${containers.home}/balancer/wildfly-balancer-${project.version}</keycloak.balancer.home>
+                <keycloak.backend1.home>${containers.home}/node1/keycloak-${project.version}</keycloak.backend1.home>
+                <keycloak.backend2.home>${containers.home}/node2/keycloak-${project.version}</keycloak.backend2.home>
+
+                <keycloak.home>${keycloak.backend1.home}</keycloak.home>
+                <jboss.server.config.dir>${keycloak.home}/standalone/configuration</jboss.server.config.dir>
+            </properties>
+            <dependencies>
+                <dependency>
+                    <groupId>org.wildfly</groupId>
+                    <artifactId>wildfly-arquillian-container-managed</artifactId>
+                </dependency>
+            </dependencies>
+            <build>
+                <pluginManagement>
+                    <plugins>
+                        <plugin>
+                            <groupId>org.apache.maven.plugins</groupId>
+                            <artifactId>maven-surefire-plugin</artifactId>
+                            <configuration>
+                                <systemPropertyVariables>
+                                    <run.h2>true</run.h2>
+                                    
+                                    <auth.server.wildfly.cluster>true</auth.server.wildfly.cluster>
+                                    <auth.server.undertow>false</auth.server.undertow>
+                                    <adapter.test.props>${adapter.test.props}</adapter.test.props>
+                                    
+                                    <keycloak.balancer.home>${keycloak.balancer.home}</keycloak.balancer.home>
+                                    <keycloak.backend1.home>${keycloak.backend1.home}</keycloak.backend1.home>
+                                    <keycloak.backend2.home>${keycloak.backend2.home}</keycloak.backend2.home>
+
+                                    <!--100-->
+                                    <auth.server.backend1.port.offset>101</auth.server.backend1.port.offset>
+                                    <auth.server.backend2.port.offset>102</auth.server.backend2.port.offset>
+                                    <!--8180-->
+                                    <auth.server.backend1.http.port>8181</auth.server.backend1.http.port>
+                                    <auth.server.backend2.http.port>8182</auth.server.backend2.http.port>
+                                    <!--8543-->
+                                    <auth.server.backend1.https.port>8544</auth.server.backend1.https.port>
+                                    <auth.server.backend2.https.port>8545</auth.server.backend2.https.port>
+                                    <!--10090-->
+                                    <auth.server.backend1.management.port>10091</auth.server.backend1.management.port>
+                                    <auth.server.backend2.management.port>10092</auth.server.backend2.management.port>
+                                    <!--10099-->
+                                    <auth.server.backend1.management.port.jmx>10100</auth.server.backend1.management.port.jmx>
+                                    <auth.server.backend2.management.port.jmx>10101</auth.server.backend2.management.port.jmx>
+                                </systemPropertyVariables>
+                            </configuration>
+                        </plugin>
+                        <plugin>
+                            <groupId>org.apache.maven.plugins</groupId>
+                            <artifactId>maven-dependency-plugin</artifactId>
+                            <executions>
+                                <execution>
+                                    <id>unpack-auth-server-wildfly</id>
+                                    <phase>generate-test-resources</phase>
+                                    <goals>
+                                        <goal>unpack</goal>
+                                    </goals>
+                                    <configuration>
+                                        <artifactItems>
+                                            <artifactItem>
+                                                <groupId>org.keycloak.testsuite</groupId>
+                                                <artifactId>integration-arquillian-server-wildfly-balancer</artifactId>
+                                                <version>${project.version}</version>
+                                                <type>zip</type>
+                                                <outputDirectory>${containers.home}/balancer</outputDirectory>
+                                            </artifactItem>
+                                            <artifactItem>
+                                                <groupId>org.keycloak.testsuite</groupId>
+                                                <artifactId>integration-arquillian-server-wildfly</artifactId>
+                                                <version>${project.version}</version>
+                                                <type>zip</type>
+                                                <outputDirectory>${containers.home}/node1</outputDirectory>
+                                            </artifactItem>
+                                            <artifactItem>
+                                                <groupId>org.keycloak.testsuite</groupId>
+                                                <artifactId>integration-arquillian-server-wildfly</artifactId>
+                                                <version>${project.version}</version>
+                                                <type>zip</type>
+                                                <outputDirectory>${containers.home}/node2</outputDirectory>
+                                            </artifactItem>
+                                        </artifactItems>
+                                        <overWriteIfNewer>true</overWriteIfNewer>
+                                    </configuration>
+                                </execution>
+                            </executions>
+                        </plugin>
+                    </plugins>
+                </pluginManagement>
+            </build>
+        </profile>
+
         <profile>
             <id>auth-server-eap7</id>
             <properties>
@@ -538,6 +665,10 @@
                     <name>migrated.auth.server.version</name>
                 </property>
             </activation>
+            <properties>
+                <!--diable exclusion pattern for migration tests, which is enabled by default in the base/pom.xml-->
+                <exclude.migration>-</exclude.migration>
+            </properties>
             <build>
                 <plugins>
                     <plugin>
@@ -743,7 +874,7 @@
                 </pluginManagement>
             </build>
         </profile>
-        
+                
     </profiles>
     
 </project>
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index bb2782d..6d547f7 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -831,6 +831,7 @@ additional-grants=Additional Grants
 revoke=Revoke
 new-password=New Password
 password-confirmation=Password Confirmation
+reset-password=Reset Password
 credentials.temporary.tooltip=If enabled user is required to change password on next login
 remove-totp=Remove TOTP
 credentials.remove-totp.tooltip=Remove one time password generator for user.
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/app.js b/themes/src/main/resources/theme/base/admin/resources/js/app.js
index 69df83e..ca78bac 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/app.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/app.js
@@ -6,6 +6,8 @@ consoleBaseUrl = consoleBaseUrl + "/console";
 var configUrl = consoleBaseUrl + "/config";
 
 var auth = {};
+var resourceBundle;
+var locale = 'en';
 
 var module = angular.module('keycloak', [ 'keycloak.services', 'keycloak.loaders', 'ui.bootstrap', 'ui.select2', 'angularFileUpload', 'angularTreeview', 'pascalprecht.translate', 'ngCookies', 'ngSanitize']);
 var resourceRequests = 0;
@@ -34,6 +36,25 @@ angular.element(document).ready(function () {
         req.send();
     }
 
+    function loadResourceBundle(success, error) {
+        var req = new XMLHttpRequest();
+        req.open('GET', consoleBaseUrl + '/messages.json?lang=' + locale, true);
+        req.setRequestHeader('Accept', 'application/json');
+
+        req.onreadystatechange = function () {
+            if (req.readyState == 4) {
+                if (req.status == 200) {
+                    var data = JSON.parse(req.responseText);
+                    success && success(data);
+                } else {
+                    error && error();
+                }
+            }
+        }
+
+        req.send();
+    }
+
     function hasAnyAccess(user) {
         return user && user['realm_access'];
     }
@@ -45,6 +66,10 @@ angular.element(document).ready(function () {
     keycloakAuth.init({ onLoad: 'login-required' }).success(function () {
         auth.authz = keycloakAuth;
 
+        if (auth.authz.idTokenParsed.locale) {
+            locale = auth.authz.idTokenParsed.locale;
+        }
+
         auth.refreshPermissions = function(success, error) {
             whoAmI(function(data) {
                 auth.user = data;
@@ -57,17 +82,19 @@ angular.element(document).ready(function () {
             });
         };
 
-        auth.refreshPermissions(function() {
-            module.factory('Auth', function() {
-                return auth;
-            });
-            var injector = angular.bootstrap(document, ["keycloak"]);
+        loadResourceBundle(function(data) {
+            resourceBundle = data;
+
+            auth.refreshPermissions(function () {
+                module.factory('Auth', function () {
+                    return auth;
+                });
+                var injector = angular.bootstrap(document, ["keycloak"]);
 
-            injector.get('$translate')('consoleTitle').then(function(consoleTitle) {
-                document.title=consoleTitle;
+                injector.get('$translate')('consoleTitle').then(function (consoleTitle) {
+                    document.title = consoleTitle;
+                });
             });
-        }, function() {
-            window.location.reload();
         });
     }).error(function () {
         window.location.reload();
@@ -99,15 +126,8 @@ module.factory('authInterceptor', function($q, Auth) {
 
 module.config(['$translateProvider', function($translateProvider) {
     $translateProvider.useSanitizeValueStrategy('sanitizeParameters');
-    
-    var locale = auth.authz.idTokenParsed.locale;
-    if (locale !== undefined) {
-        $translateProvider.preferredLanguage(locale);
-    } else {
-        $translateProvider.preferredLanguage('en');
-    }
-    
-    $translateProvider.useUrlLoader('messages.json');
+    $translateProvider.preferredLanguage(locale);
+    $translateProvider.translations(locale, resourceBundle);
 }]);
 
 module.config([ '$routeProvider', function($routeProvider) {
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
index 589b4f6..c93e8c6 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
@@ -25,19 +25,19 @@ module.controller('GlobalCtrl', function($scope, $http, Auth, Current, $location
         },
 
         get viewRealm() {
-            return getAccess('view-realm') || this.manageRealm;
+            return getAccess('view-realm') || getAccess('manage-realm') || this.manageRealm;
         },
 
         get viewClients() {
-            return getAccess('view-clients') || this.manageClients;
+            return getAccess('view-clients') || getAccess('manage-clients') || this.manageClients;
         },
 
         get viewUsers() {
-            return getAccess('view-users') || this.manageClients;
+            return getAccess('view-users') || getAccess('manage-users') || this.manageClients;
         },
 
         get viewEvents() {
-            return getAccess('view-events') || this.manageClients;
+            return getAccess('view-events') || getAccess('manage-events') || this.manageClients;
         },
 
         get manageRealm() {