keycloak-uncached

Merge branch 'upstream/master' into fork/master * upstream_master:

11/24/2015 8:52:24 PM

Changes

.travis.yml 10(+2 -8)

client-api/src/main/java/org/keycloak/client/registration/ClientRegistration.java 275(+0 -275)

connections/file/src/main/java/org/keycloak/connections/file/DefaultFileConnectionProvider.java 86(+0 -86)

connections/file/src/main/java/org/keycloak/connections/file/DefaultFileConnectionProviderFactory.java 210(+0 -210)

connections/file/src/main/java/org/keycloak/connections/file/FileConnectionProvider.java 31(+0 -31)

connections/file/src/main/java/org/keycloak/connections/file/FileConnectionProviderFactory.java 25(+0 -25)

connections/file/src/main/java/org/keycloak/connections/file/FileConnectionSpi.java 32(+0 -32)

connections/file/src/main/java/org/keycloak/connections/file/InMemoryModel.java 109(+0 -109)

connections/file/src/main/java/org/keycloak/connections/file/Model.java 30(+0 -30)

connections/file/src/main/resources/META-INF/services/org.keycloak.connections.file.FileConnectionProviderFactory 1(+0 -1)

connections/file/src/main/resources/META-INF/services/org.keycloak.provider.Spi 1(+0 -1)

core/src/main/java/org/keycloak/representations/idm/UserRoleMappingRepresentation.java 45(+0 -45)

distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-connections-file/main/module.xml 20(+0 -20)

distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-model-file/main/module.xml 18(+0 -18)

distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-connections-file/main/module.xml 20(+0 -20)

distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-model-file/main/module.xml 16(+0 -16)

model/file/pom.xml 62(+0 -62)

model/file/src/main/java/org/keycloak/models/file/adapter/ClientAdapter.java 663(+0 -663)

model/file/src/main/java/org/keycloak/models/file/adapter/MigrationModelAdapter.java 26(+0 -26)

model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java 1790(+0 -1790)

model/file/src/main/java/org/keycloak/models/file/adapter/RoleAdapter.java 188(+0 -188)

model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java 589(+0 -589)

model/file/src/main/java/org/keycloak/models/file/FileRealmProvider.java 113(+0 -113)

model/file/src/main/java/org/keycloak/models/file/FileUserProvider.java 504(+0 -504)

model/file/src/main/resources/META-INF/services/org.keycloak.models.RealmProviderFactory 1(+0 -1)

model/file/src/main/resources/META-INF/services/org.keycloak.models.UserProviderFactory 1(+0 -1)

model/pom.xml 1(+0 -1)

pom.xml 33(+17 -16)

saml/saml-core/src/main/java/org/keycloak/saml/common/util/PBEUtils.java 85(+0 -85)

services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCClientRepresentation.java 45(+0 -45)

services/src/main/java/org/keycloak/services/clientregistration/OIDCClientRegistrationProvider.java 34(+0 -34)

services/src/main/java/org/keycloak/services/resources/admin/AdminMessagesLoader.java 107(+0 -107)

testsuite/integration-arquillian/servers/wildfly_kc13/pom.xml 199(+0 -199)

testsuite/integration-arquillian/servers/wildfly_kc14/pom.xml 199(+0 -199)

testsuite/integration-arquillian/servers/wildfly_kc15/pom.xml 199(+0 -199)

testsuite/integration-arquillian/servers/wildfly_kc16/pom.xml 199(+0 -199)

testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jira/JBossJiraParser.java 43(+0 -43)

testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jira/Jira.java 28(+0 -28)

testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jira/JiraTestExecutionDecider.java 61(+0 -61)

testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/jira/Status.java 36(+0 -36)

testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/Bindings.java 146(+0 -146)

testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/OTPPolicy.java 85(+0 -85)

Details

.travis.yml 10(+2 -8)

diff --git a/.travis.yml b/.travis.yml
index 8146d6e..5dee839 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,14 +3,8 @@ language: java
 jdk:
   - oraclejdk8
 
-cache:
-    directories:
-        - $HOME/.m2
-
-before_cache:
-  - rm -rf $HOME/.m2/repository/org/keycloak
-
-install: mvn install -Pdistribution -DskipTests=true -B -V
+install: 
+  - travis_wait mvn install -Pdistribution -DskipTests=true -B -V -q
 
 script:
   - mvn test -B
diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java b/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java
index 6da630f..a677419 100755
--- a/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java
+++ b/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java
@@ -92,4 +92,9 @@ public abstract class AbstractIdentityProvider<C extends IdentityProviderModel> 
     public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, BrokeredIdentityContext context) {
 
     }
+
+    @Override
+    public IdentityProviderDataMarshaller getMarshaller() {
+        return new DefaultDataMarshaller();
+    }
 }
diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/DefaultDataMarshaller.java b/broker/core/src/main/java/org/keycloak/broker/provider/DefaultDataMarshaller.java
new file mode 100644
index 0000000..3f8fcf2
--- /dev/null
+++ b/broker/core/src/main/java/org/keycloak/broker/provider/DefaultDataMarshaller.java
@@ -0,0 +1,40 @@
+package org.keycloak.broker.provider;
+
+import java.io.IOException;
+
+import org.keycloak.common.util.Base64Url;
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class DefaultDataMarshaller implements IdentityProviderDataMarshaller {
+
+    @Override
+    public String serialize(Object value) {
+        if (value instanceof String) {
+            return (String) value;
+        } else {
+            try {
+                byte[] bytes = JsonSerialization.writeValueAsBytes(value);
+                return Base64Url.encode(bytes);
+            } catch (IOException ioe) {
+                throw new RuntimeException(ioe);
+            }
+        }
+    }
+
+    @Override
+    public <T> T deserialize(String serialized, Class<T> clazz) {
+        if (clazz.equals(String.class)) {
+            return clazz.cast(serialized);
+        } else {
+            byte[] bytes = Base64Url.decode(serialized);
+            try {
+                return JsonSerialization.readValue(bytes, clazz);
+            } catch (IOException ioe) {
+                throw new RuntimeException(ioe);
+            }
+        }
+    }
+}
diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProvider.java b/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProvider.java
index 1d775ee..42eb6fe 100755
--- a/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProvider.java
+++ b/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProvider.java
@@ -103,4 +103,10 @@ public interface IdentityProvider<C extends IdentityProviderModel> extends Provi
      */
     Response export(UriInfo uriInfo, RealmModel realm, String format);
 
+    /**
+     * Implementation of marshaller to serialize/deserialize attached data to Strings, which can be saved in clientSession
+     * @return
+     */
+    IdentityProviderDataMarshaller getMarshaller();
+
 }
diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProviderDataMarshaller.java b/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProviderDataMarshaller.java
new file mode 100644
index 0000000..7e57653
--- /dev/null
+++ b/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProviderDataMarshaller.java
@@ -0,0 +1,12 @@
+package org.keycloak.broker.provider;
+
+/**
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface IdentityProviderDataMarshaller {
+
+    String serialize(Object obj);
+    <T> T deserialize(String serialized, Class<T> clazz);
+
+}
diff --git a/broker/saml/pom.xml b/broker/saml/pom.xml
index 858a7ba..21a0099 100755
--- a/broker/saml/pom.xml
+++ b/broker/saml/pom.xml
@@ -45,6 +45,11 @@
             <artifactId>jboss-logging</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
 </project>
diff --git a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLDataMarshaller.java b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLDataMarshaller.java
new file mode 100644
index 0000000..61f4d8a
--- /dev/null
+++ b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLDataMarshaller.java
@@ -0,0 +1,88 @@
+package org.keycloak.broker.saml;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+
+import javax.xml.stream.XMLEventReader;
+
+import org.keycloak.broker.provider.DefaultDataMarshaller;
+import org.keycloak.dom.saml.v2.assertion.AssertionType;
+import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
+import org.keycloak.dom.saml.v2.protocol.ResponseType;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.exceptions.ProcessingException;
+import org.keycloak.saml.common.util.StaxUtil;
+import org.keycloak.saml.processing.core.parsers.saml.SAMLParser;
+import org.keycloak.saml.processing.core.parsers.util.SAMLParserUtil;
+import org.keycloak.saml.processing.core.saml.v2.writers.SAMLAssertionWriter;
+import org.keycloak.saml.processing.core.saml.v2.writers.SAMLResponseWriter;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class SAMLDataMarshaller extends DefaultDataMarshaller {
+
+    @Override
+    public String serialize(Object obj) {
+
+        // Lame impl, but hopefully sufficient for now. See if something better is needed...
+        if (obj.getClass().getName().startsWith("org.keycloak.dom.saml")) {
+            ByteArrayOutputStream bos = new ByteArrayOutputStream();
+
+            try {
+                if (obj instanceof ResponseType) {
+                    ResponseType responseType = (ResponseType) obj;
+                    SAMLResponseWriter samlWriter = new SAMLResponseWriter(StaxUtil.getXMLStreamWriter(bos));
+                    samlWriter.write(responseType);
+                } else if (obj instanceof AssertionType) {
+                    AssertionType assertion = (AssertionType) obj;
+                    SAMLAssertionWriter samlWriter = new SAMLAssertionWriter(StaxUtil.getXMLStreamWriter(bos));
+                    samlWriter.write(assertion);
+                } else if (obj instanceof AuthnStatementType) {
+                    AuthnStatementType authnStatement = (AuthnStatementType) obj;
+                    SAMLAssertionWriter samlWriter = new SAMLAssertionWriter(StaxUtil.getXMLStreamWriter(bos));
+                    samlWriter.write(authnStatement, true);
+                } else {
+                    throw new IllegalArgumentException("Don't know how to serialize object of type " + obj.getClass().getName());
+                }
+            } catch (ProcessingException pe) {
+                throw new RuntimeException(pe);
+            }
+
+            return new String(bos.toByteArray());
+        } else {
+            return super.serialize(obj);
+        }
+    }
+
+    @Override
+    public <T> T deserialize(String serialized, Class<T> clazz) {
+        if (clazz.getName().startsWith("org.keycloak.dom.saml")) {
+            String xmlString = serialized;
+
+            try {
+                if (clazz.equals(ResponseType.class) || clazz.equals(AssertionType.class)) {
+                    byte[] bytes = xmlString.getBytes();
+                    InputStream is = new ByteArrayInputStream(bytes);
+                    Object respType = new SAMLParser().parse(is);
+                    return clazz.cast(respType);
+                } else if (clazz.equals(AuthnStatementType.class)) {
+                    byte[] bytes = xmlString.getBytes();
+                    InputStream is = new ByteArrayInputStream(bytes);
+                    XMLEventReader xmlEventReader = new SAMLParser().createEventReader(is);
+                    AuthnStatementType authnStatement = SAMLParserUtil.parseAuthnStatement(xmlEventReader);
+                    return clazz.cast(authnStatement);
+                } else {
+                    throw new IllegalArgumentException("Don't know how to deserialize object of type " + clazz.getName());
+                }
+            } catch (ParsingException pe) {
+                throw new RuntimeException(pe);
+            }
+
+        } else {
+            return super.deserialize(serialized, clazz);
+        }
+    }
+
+}
diff --git a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
index 248c4de..0014470 100755
--- a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
+++ b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
@@ -22,6 +22,7 @@ import org.keycloak.broker.provider.AbstractIdentityProvider;
 import org.keycloak.broker.provider.AuthenticationRequest;
 import org.keycloak.broker.provider.BrokeredIdentityContext;
 import org.keycloak.broker.provider.IdentityBrokerException;
+import org.keycloak.broker.provider.IdentityProviderDataMarshaller;
 import org.keycloak.broker.provider.util.SimpleHttp;
 import org.keycloak.dom.saml.v2.assertion.AssertionType;
 import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
@@ -263,4 +264,8 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
         return SignatureAlgorithm.RSA_SHA256;
     }
 
+    @Override
+    public IdentityProviderDataMarshaller getMarshaller() {
+        return new SAMLDataMarshaller();
+    }
 }
diff --git a/broker/saml/src/test/java/org/keycloak/broker/saml/SAMLDataMarshallerTest.java b/broker/saml/src/test/java/org/keycloak/broker/saml/SAMLDataMarshallerTest.java
new file mode 100644
index 0000000..cf6e4f8
--- /dev/null
+++ b/broker/saml/src/test/java/org/keycloak/broker/saml/SAMLDataMarshallerTest.java
@@ -0,0 +1,64 @@
+package org.keycloak.broker.saml;
+
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.dom.saml.v2.assertion.AssertionType;
+import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
+import org.keycloak.dom.saml.v2.assertion.NameIDType;
+import org.keycloak.dom.saml.v2.protocol.ResponseType;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class SAMLDataMarshallerTest {
+
+    private static final String TEST_RESPONSE = "<samlp:Response xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"ID_4804cf50-cd96-4b92-823e-89adaa0c78ba\" Version=\"2.0\" IssueInstant=\"2015-11-06T11:00:33.920Z\" Destination=\"http://localhost:8081/auth/realms/realm-with-broker/broker/kc-saml-idp-basic/endpoint\" InResponseTo=\"ID_c6b90123-f0bb-4c5c-bf9d-388d5bbe467a\"><saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">http://localhost:8082/auth/realms/realm-with-saml-idp-basic</saml:Issuer><samlp:Status><samlp:StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"></samlp:StatusCode></samlp:Status><saml:Assertion xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"ID_29b196c2-d641-45c8-a423-8ed8e54d4cf9\" Version=\"2.0\" IssueInstant=\"2015-11-06T11:00:33.911Z\"><saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">http://localhost:8082/auth/realms/realm-with-saml-idp-basic</saml:Issuer><saml:Subject><saml:NameID xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\">test-user</saml:NameID><saml:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><saml:SubjectConfirmationData InResponseTo=\"ID_c6b90123-f0bb-4c5c-bf9d-388d5bbe467a\" NotOnOrAfter=\"2015-11-06T11:05:31.911Z\" Recipient=\"http://localhost:8081/auth/realms/realm-with-broker/broker/kc-saml-idp-basic/endpoint\"></saml:SubjectConfirmationData></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore=\"2015-11-06T11:00:31.911Z\" NotOnOrAfter=\"2015-11-06T11:01:31.911Z\"><saml:AudienceRestriction><saml:Audience>http://localhost:8081/auth/realms/realm-with-broker</saml:Audience></saml:AudienceRestriction></saml:Conditions><saml:AuthnStatement AuthnInstant=\"2015-11-06T11:00:33.923Z\" SessionIndex=\"fa0f4fd3-8a11-44f4-9acb-ee30c5bb8fe5\"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement><saml:AttributeStatement><saml:Attribute Name=\"mobile\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xsi:type=\"xs:string\">617-666-7777</saml:AttributeValue></saml:Attribute><saml:Attribute Name=\"urn:oid:1.2.840.113549.1.9.1\" FriendlyName=\"email\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xsi:type=\"xs:string\">test-user@localhost</saml:AttributeValue></saml:Attribute></saml:AttributeStatement><saml:AttributeStatement><saml:Attribute Name=\"Role\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xsi:type=\"xs:string\">manager</saml:AttributeValue></saml:Attribute></saml:AttributeStatement></saml:Assertion></samlp:Response>";
+
+    private static final String TEST_ASSERTION = "<saml:Assertion xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"ID_29b196c2-d641-45c8-a423-8ed8e54d4cf9\" Version=\"2.0\" IssueInstant=\"2015-11-06T11:00:33.911Z\"><saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">http://localhost:8082/auth/realms/realm-with-saml-idp-basic</saml:Issuer><saml:Subject><saml:NameID xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\">test-user</saml:NameID><saml:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><saml:SubjectConfirmationData InResponseTo=\"ID_c6b90123-f0bb-4c5c-bf9d-388d5bbe467a\" NotOnOrAfter=\"2015-11-06T11:05:31.911Z\" Recipient=\"http://localhost:8081/auth/realms/realm-with-broker/broker/kc-saml-idp-basic/endpoint\"></saml:SubjectConfirmationData></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore=\"2015-11-06T11:00:31.911Z\" NotOnOrAfter=\"2015-11-06T11:01:31.911Z\"><saml:AudienceRestriction><saml:Audience>http://localhost:8081/auth/realms/realm-with-broker</saml:Audience></saml:AudienceRestriction></saml:Conditions><saml:AuthnStatement AuthnInstant=\"2015-11-06T11:00:33.923Z\" SessionIndex=\"fa0f4fd3-8a11-44f4-9acb-ee30c5bb8fe5\"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement><saml:AttributeStatement><saml:Attribute Name=\"mobile\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xsi:type=\"xs:string\">617-666-7777</saml:AttributeValue></saml:Attribute><saml:Attribute Name=\"urn:oid:1.2.840.113549.1.9.1\" FriendlyName=\"email\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xsi:type=\"xs:string\">test-user@localhost</saml:AttributeValue></saml:Attribute></saml:AttributeStatement><saml:AttributeStatement><saml:Attribute Name=\"Role\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xsi:type=\"xs:string\">manager</saml:AttributeValue></saml:Attribute></saml:AttributeStatement></saml:Assertion>";
+
+    private static final String TEST_AUTHN_TYPE = "<saml:AuthnStatement xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" AuthnInstant=\"2015-11-06T11:00:33.923Z\" SessionIndex=\"fa0f4fd3-8a11-44f4-9acb-ee30c5bb8fe5\"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement>";
+
+    @Test
+    public void testParseResponse() throws Exception {
+        SAMLDataMarshaller serializer = new SAMLDataMarshaller();
+        ResponseType responseType = serializer.deserialize(TEST_RESPONSE, ResponseType.class);
+
+        // test ResponseType
+        Assert.assertEquals(responseType.getID(), "ID_4804cf50-cd96-4b92-823e-89adaa0c78ba");
+        Assert.assertEquals(responseType.getDestination(), "http://localhost:8081/auth/realms/realm-with-broker/broker/kc-saml-idp-basic/endpoint");
+        Assert.assertEquals(responseType.getIssuer().getValue(), "http://localhost:8082/auth/realms/realm-with-saml-idp-basic");
+        Assert.assertEquals(responseType.getAssertions().get(0).getID(), "ID_29b196c2-d641-45c8-a423-8ed8e54d4cf9");
+
+        // back to String
+        String serialized = serializer.serialize(responseType);
+        Assert.assertEquals(TEST_RESPONSE, serialized);
+    }
+
+    @Test
+    public void testParseAssertion() throws Exception {
+        SAMLDataMarshaller serializer = new SAMLDataMarshaller();
+        AssertionType assertion = serializer.deserialize(TEST_ASSERTION, AssertionType.class);
+
+        // test assertion
+        Assert.assertEquals(assertion.getID(), "ID_29b196c2-d641-45c8-a423-8ed8e54d4cf9");
+        Assert.assertEquals(((NameIDType) assertion.getSubject().getSubType().getBaseID()).getValue(), "test-user");
+
+        // back to String
+        String serialized = serializer.serialize(assertion);
+        Assert.assertEquals(TEST_ASSERTION, serialized);
+    }
+
+    @Test
+    public void testParseAuthnType() throws Exception {
+        SAMLDataMarshaller serializer = new SAMLDataMarshaller();
+        AuthnStatementType authnStatement =  serializer.deserialize(TEST_AUTHN_TYPE, AuthnStatementType.class);
+
+        // test authnStatement
+        Assert.assertEquals(authnStatement.getSessionIndex(), "fa0f4fd3-8a11-44f4-9acb-ee30c5bb8fe5");
+
+        // back to String
+        String serialized = serializer.serialize(authnStatement);
+        Assert.assertEquals(TEST_AUTHN_TYPE, serialized);
+    }
+}
diff --git a/client-registration/api/src/main/java/org/keycloak/client/registration/Auth.java b/client-registration/api/src/main/java/org/keycloak/client/registration/Auth.java
new file mode 100644
index 0000000..c3dd313
--- /dev/null
+++ b/client-registration/api/src/main/java/org/keycloak/client/registration/Auth.java
@@ -0,0 +1,68 @@
+package org.keycloak.client.registration;
+
+import org.apache.http.HttpHeaders;
+import org.apache.http.HttpRequest;
+import org.keycloak.common.util.Base64;
+import org.keycloak.representations.idm.ClientInitialAccessPresentation;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.oidc.OIDCClientRepresentation;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public abstract class Auth {
+
+    public abstract void addAuth(HttpRequest request);
+
+    public static Auth token(String token) {
+        return new BearerTokenAuth(token);
+    }
+
+    public static Auth token(ClientInitialAccessPresentation initialAccess) {
+        return new BearerTokenAuth(initialAccess.getToken());
+    }
+
+    public static Auth token(ClientRepresentation client) {
+        return new BearerTokenAuth(client.getRegistrationAccessToken());
+    }
+
+    public static Auth token(OIDCClientRepresentation client) {
+        return new BearerTokenAuth(client.getRegistrationAccessToken());
+    }
+
+    public static Auth client(String clientId, String clientSecret) {
+        return new BasicAuth(clientId, clientSecret);
+    }
+
+    private static class BearerTokenAuth extends Auth {
+
+        private String token;
+
+        public BearerTokenAuth(String token) {
+            this.token = token;
+        }
+
+        @Override
+        public void addAuth(HttpRequest request) {
+            request.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token);
+        }
+    }
+
+    private static class BasicAuth extends Auth {
+
+        private String username;
+        private String password;
+
+        public BasicAuth(String username, String password) {
+            this.username = username;
+            this.password = password;
+        }
+
+        @Override
+        public void addAuth(HttpRequest request) {
+            String val = Base64.encodeBytes((username + ":" + password).getBytes());
+            request.setHeader(HttpHeaders.AUTHORIZATION, "Basic " + val);
+        }
+    }
+
+}
diff --git a/client-registration/api/src/main/java/org/keycloak/client/registration/ClientRegistration.java b/client-registration/api/src/main/java/org/keycloak/client/registration/ClientRegistration.java
new file mode 100644
index 0000000..f2215f1
--- /dev/null
+++ b/client-registration/api/src/main/java/org/keycloak/client/registration/ClientRegistration.java
@@ -0,0 +1,186 @@
+package org.keycloak.client.registration;
+
+import org.apache.http.client.HttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.keycloak.representations.adapters.config.AdapterConfig;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.oidc.OIDCClientRepresentation;
+import org.keycloak.util.JsonSerialization;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientRegistration {
+
+    public static final ObjectMapper outputMapper = new ObjectMapper();
+    static {
+        outputMapper.getSerializationConfig().addMixInAnnotations(ClientRepresentation.class, ClientRepresentationMixIn.class);
+        outputMapper.getSerializationConfig().addMixInAnnotations(OIDCClientRepresentation.class, OIDCClientRepresentationMixIn.class);
+        outputMapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
+    }
+
+    private final String JSON = "application/json";
+    private final String XML = "application/xml";
+
+    private final String DEFAULT = "default";
+    private final String INSTALLATION = "install";
+    private final String OIDC = "openid-connect";
+    private final String SAML = "saml2-entity-descriptor";
+
+    private HttpUtil httpUtil;
+
+    public static ClientRegistrationBuilder create() {
+        return new ClientRegistrationBuilder();
+    }
+
+    ClientRegistration(HttpUtil httpUtil) {
+        this.httpUtil = httpUtil;
+    }
+
+    public void close() throws ClientRegistrationException {
+        if (httpUtil != null) {
+            httpUtil.close();
+        }
+        httpUtil = null;
+    }
+
+    public ClientRegistration auth(Auth auth) {
+        httpUtil.setAuth(auth);
+        return this;
+    }
+
+    public ClientRepresentation create(ClientRepresentation client) throws ClientRegistrationException {
+        String content = serialize(client);
+        InputStream resultStream = httpUtil.doPost(content, JSON, JSON, DEFAULT);
+        return deserialize(resultStream, ClientRepresentation.class);
+    }
+
+    public ClientRepresentation get(String clientId) throws ClientRegistrationException {
+        InputStream resultStream = httpUtil.doGet(JSON, DEFAULT, clientId);
+        return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null;
+    }
+
+    public AdapterConfig getAdapterConfig(String clientId) throws ClientRegistrationException {
+        InputStream resultStream = httpUtil.doGet(JSON, INSTALLATION, clientId);
+        return resultStream != null ? deserialize(resultStream, AdapterConfig.class) : null;
+    }
+
+    public ClientRepresentation update(ClientRepresentation client) throws ClientRegistrationException {
+        String content = serialize(client);
+        InputStream resultStream = httpUtil.doPut(content, JSON, JSON, DEFAULT, client.getClientId());
+        return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null;
+    }
+
+    public void delete(ClientRepresentation client) throws ClientRegistrationException {
+        delete(client.getClientId());
+    }
+
+    public void delete(String clientId) throws ClientRegistrationException {
+        httpUtil.doDelete(DEFAULT, clientId);
+    }
+
+    public OIDCClientRegistration oidc() {
+        return new OIDCClientRegistration();
+    }
+
+    public SAMLClientRegistration saml() {
+        return new SAMLClientRegistration();
+    }
+
+    public static String serialize(Object obj) throws ClientRegistrationException {
+        try {
+            return outputMapper.writeValueAsString(obj);
+        } catch (IOException e) {
+            throw new ClientRegistrationException("Failed to write json object", e);
+        }
+    }
+
+    private static <T> T deserialize(InputStream inputStream, Class<T> clazz) throws ClientRegistrationException {
+        try {
+            return JsonSerialization.readValue(inputStream, clazz);
+        } catch (IOException e) {
+            throw new ClientRegistrationException("Failed to read json object", e);
+        }
+    }
+
+    public class OIDCClientRegistration {
+
+        public OIDCClientRepresentation create(OIDCClientRepresentation client) throws ClientRegistrationException {
+            String content = serialize(client);
+            InputStream resultStream = httpUtil.doPost(content, JSON, JSON, OIDC);
+            return deserialize(resultStream, OIDCClientRepresentation.class);
+        }
+
+        public OIDCClientRepresentation get(String clientId) throws ClientRegistrationException {
+            InputStream resultStream = httpUtil.doGet(JSON, OIDC, clientId);
+            return resultStream != null ? deserialize(resultStream, OIDCClientRepresentation.class) : null;
+        }
+
+        public OIDCClientRepresentation update(OIDCClientRepresentation client) throws ClientRegistrationException {
+            String content = serialize(client);
+            InputStream resultStream = httpUtil.doPut(content, JSON, JSON, OIDC, client.getClientId());
+            return resultStream != null ? deserialize(resultStream, OIDCClientRepresentation.class) : null;
+        }
+
+        public void delete(OIDCClientRepresentation client) throws ClientRegistrationException {
+            delete(client.getClientId());
+        }
+
+        public void delete(String clientId) throws ClientRegistrationException {
+            httpUtil.doDelete(OIDC, clientId);
+        }
+
+    }
+
+    public class SAMLClientRegistration {
+
+        public ClientRepresentation create(String entityDescriptor) throws ClientRegistrationException {
+            InputStream resultStream = httpUtil.doPost(entityDescriptor, XML, JSON, SAML);
+            return deserialize(resultStream, ClientRepresentation.class);
+        }
+
+    }
+
+    public static class ClientRegistrationBuilder {
+
+        private String url;
+        private HttpClient httpClient;
+
+        ClientRegistrationBuilder() {
+        }
+
+        public ClientRegistrationBuilder url(String realmUrl) {
+            url = realmUrl;
+            return this;
+        }
+
+        public ClientRegistrationBuilder url(String authUrl, String realm) {
+            url = HttpUtil.getUrl(authUrl, "realms", realm, "clients");
+            return this;
+        }
+
+        public ClientRegistrationBuilder httpClient(HttpClient httpClient) {
+            this.httpClient = httpClient;
+            return this;
+        }
+
+        public ClientRegistration build() {
+            if (url == null) {
+                throw new IllegalStateException("url not configured");
+            }
+
+            if (httpClient == null) {
+                httpClient = HttpClients.createDefault();
+            }
+
+            return new ClientRegistration(new HttpUtil(httpClient, url));
+        }
+
+    }
+
+}
diff --git a/client-registration/api/src/main/java/org/keycloak/client/registration/ClientRepresentationMixIn.java b/client-registration/api/src/main/java/org/keycloak/client/registration/ClientRepresentationMixIn.java
new file mode 100644
index 0000000..ba382f6
--- /dev/null
+++ b/client-registration/api/src/main/java/org/keycloak/client/registration/ClientRepresentationMixIn.java
@@ -0,0 +1,13 @@
+package org.keycloak.client.registration;
+
+import org.codehaus.jackson.annotate.JsonIgnore;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+abstract class ClientRepresentationMixIn {
+
+    @JsonIgnore
+    String registrationAccessToken;
+
+}
diff --git a/client-registration/api/src/main/java/org/keycloak/client/registration/HttpUtil.java b/client-registration/api/src/main/java/org/keycloak/client/registration/HttpUtil.java
new file mode 100644
index 0000000..4d44710
--- /dev/null
+++ b/client-registration/api/src/main/java/org/keycloak/client/registration/HttpUtil.java
@@ -0,0 +1,171 @@
+package org.keycloak.client.registration;
+
+import org.apache.http.HttpHeaders;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.*;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.keycloak.client.registration.Auth;
+import org.keycloak.client.registration.ClientRegistrationException;
+import org.keycloak.client.registration.HttpErrorException;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+class HttpUtil {
+
+    private HttpClient httpClient;
+
+    private String baseUri;
+
+    private Auth auth;
+
+    HttpUtil(HttpClient httpClient, String baseUri) {
+        this.httpClient = httpClient;
+        this.baseUri = baseUri;
+    }
+
+    void setAuth(Auth auth) {
+        this.auth = auth;
+    }
+
+    InputStream doPost(String content, String contentType, String acceptType, String... path) throws ClientRegistrationException {
+        try {
+            HttpPost request = new HttpPost(getUrl(baseUri, path));
+
+            request.setHeader(HttpHeaders.CONTENT_TYPE, contentType);
+            request.setHeader(HttpHeaders.ACCEPT, acceptType);
+            request.setEntity(new StringEntity(content));
+
+            addAuth(request);
+
+            HttpResponse response = httpClient.execute(request);
+            InputStream responseStream = null;
+            if (response.getEntity() != null) {
+                responseStream = response.getEntity().getContent();
+            }
+
+            if (response.getStatusLine().getStatusCode() == 201) {
+                return responseStream;
+            } else {
+                responseStream.close();
+                throw new HttpErrorException(response.getStatusLine());
+            }
+        } catch (IOException e) {
+            throw new ClientRegistrationException("Failed to send request", e);
+        }
+    }
+
+    InputStream doGet(String acceptType, String... path) throws ClientRegistrationException {
+        try {
+            HttpGet request = new HttpGet(getUrl(baseUri, path));
+
+            request.setHeader(HttpHeaders.ACCEPT, acceptType);
+
+            addAuth(request);
+
+            HttpResponse response = httpClient.execute(request);
+            InputStream responseStream = null;
+            if (response.getEntity() != null) {
+                responseStream = response.getEntity().getContent();
+            }
+
+            if (response.getStatusLine().getStatusCode() == 200) {
+                return responseStream;
+            } else if (response.getStatusLine().getStatusCode() == 404) {
+                responseStream.close();
+                return null;
+            } else {
+                if (responseStream != null) {
+                    responseStream.close();
+                }
+                throw new HttpErrorException(response.getStatusLine());
+            }
+        } catch (IOException e) {
+            throw new ClientRegistrationException("Failed to send request", e);
+        }
+    }
+
+    InputStream doPut(String content, String contentType, String acceptType, String... path) throws ClientRegistrationException {
+        try {
+            HttpPut request = new HttpPut(getUrl(baseUri, path));
+
+            request.setHeader(HttpHeaders.CONTENT_TYPE, contentType);
+            request.setHeader(HttpHeaders.ACCEPT, acceptType);
+            request.setEntity(new StringEntity(content));
+
+            addAuth(request);
+
+            HttpResponse response = httpClient.execute(request);
+            if (response.getEntity() != null) {
+                response.getEntity().getContent();
+            }
+
+            InputStream responseStream = null;
+            if (response.getEntity() != null) {
+                responseStream = response.getEntity().getContent();
+            }
+
+            if (response.getStatusLine().getStatusCode() == 200) {
+                return responseStream;
+            } else {
+                if (responseStream != null) {
+                    responseStream.close();
+                }
+                throw new HttpErrorException(response.getStatusLine());
+            }
+        } catch (IOException e) {
+            throw new ClientRegistrationException("Failed to send request", e);
+        }
+    }
+
+    void doDelete(String... path) throws ClientRegistrationException {
+        try {
+            HttpDelete request = new HttpDelete(getUrl(baseUri, path));
+
+            addAuth(request);
+
+            HttpResponse response = httpClient.execute(request);
+            if (response.getEntity() != null) {
+                response.getEntity().getContent().close();
+            }
+
+            if (response.getStatusLine().getStatusCode() != 204) {
+                throw new HttpErrorException(response.getStatusLine());
+            }
+        } catch (IOException e) {
+            throw new ClientRegistrationException("Failed to send request", e);
+        }
+    }
+
+    void close() throws ClientRegistrationException {
+        if (httpClient instanceof CloseableHttpClient) {
+            try {
+                ((CloseableHttpClient) httpClient).close();
+            } catch (IOException e) {
+                throw new ClientRegistrationException("Failed to close http client", e);
+            }
+        }
+    }
+
+    static String getUrl(String baseUri, String... path) {
+        StringBuilder s = new StringBuilder();
+        s.append(baseUri);
+        for (String p : path) {
+            s.append('/');
+            s.append(p);
+        }
+        return s.toString();
+    }
+
+    private void addAuth(HttpRequestBase request) {
+        if (auth != null) {
+            auth.addAuth(request);
+        }
+    }
+
+}
diff --git a/client-registration/api/src/main/java/org/keycloak/client/registration/OIDCClientRepresentationMixIn.java b/client-registration/api/src/main/java/org/keycloak/client/registration/OIDCClientRepresentationMixIn.java
new file mode 100644
index 0000000..b0dfed6
--- /dev/null
+++ b/client-registration/api/src/main/java/org/keycloak/client/registration/OIDCClientRepresentationMixIn.java
@@ -0,0 +1,22 @@
+package org.keycloak.client.registration;
+
+import org.codehaus.jackson.annotate.JsonIgnore;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+abstract class OIDCClientRepresentationMixIn {
+
+    @JsonIgnore
+    private Integer client_id_issued_at;
+
+    @JsonIgnore
+    private Integer client_secret_expires_at;
+
+    @JsonIgnore
+    private String registration_client_uri;
+
+    @JsonIgnore
+    private String registration_access_token;
+
+}
diff --git a/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/ClientRegistrationCLI.java b/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/ClientRegistrationCLI.java
new file mode 100644
index 0000000..986faac
--- /dev/null
+++ b/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/ClientRegistrationCLI.java
@@ -0,0 +1,70 @@
+package org.keycloak.client.registration.cli;
+
+import org.jboss.aesh.cl.parser.CommandLineParserException;
+import org.jboss.aesh.console.AeshConsole;
+import org.jboss.aesh.console.AeshConsoleBuilder;
+import org.jboss.aesh.console.Prompt;
+import org.jboss.aesh.console.command.Command;
+import org.jboss.aesh.console.command.CommandNotFoundException;
+import org.jboss.aesh.console.command.registry.AeshCommandRegistryBuilder;
+import org.jboss.aesh.console.settings.Settings;
+import org.jboss.aesh.console.settings.SettingsBuilder;
+import org.jboss.aesh.terminal.Color;
+import org.jboss.aesh.terminal.TerminalColor;
+import org.jboss.aesh.terminal.TerminalString;
+import org.keycloak.client.registration.Auth;
+import org.keycloak.client.registration.ClientRegistration;
+import org.keycloak.client.registration.cli.commands.CreateCommand;
+import org.keycloak.client.registration.cli.commands.ExitCommand;
+import org.keycloak.client.registration.cli.commands.SetupCommand;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientRegistrationCLI {
+
+    private static ClientRegistration reg;
+
+    public static void main(String[] args) throws CommandLineParserException, CommandNotFoundException {
+        reg = ClientRegistration.create().url("http://localhost:8080/auth/realms/master").build();
+        reg.auth(Auth.token("..."));
+
+        Context context = new Context();
+
+        List<Command> commands = new LinkedList<>();
+        commands.add(new SetupCommand(context));
+        commands.add(new CreateCommand(context));
+        commands.add(new ExitCommand(context));
+
+        SettingsBuilder builder = new SettingsBuilder().logging(true);
+        builder.enableMan(true).readInputrc(false);
+
+        Settings settings = builder.create();
+
+        AeshCommandRegistryBuilder commandRegistryBuilder = new AeshCommandRegistryBuilder();
+        for (Command c : commands) {
+            commandRegistryBuilder.command(c);
+        }
+
+        AeshConsole aeshConsole = new AeshConsoleBuilder()
+                .commandRegistry(commandRegistryBuilder.create())
+                .settings(settings)
+                .prompt(new Prompt(new TerminalString("[clientreg]$ ",
+                        new TerminalColor(Color.GREEN, Color.DEFAULT, Color.Intensity.BRIGHT))))
+                .create();
+
+        aeshConsole.start();
+/*
+        if (args.length > 0) {
+            CommandContainer command = registry.getCommand(args[0], null);
+            ParserGenerator.parseAndPopulate(command, args[0], Arrays.copyOfRange(args, 1, args.length));
+        }*/
+
+        //commandInvocation.getCommandRegistry().getAllCommandNames()
+    }
+
+}
+
diff --git a/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/commands/CreateCommand.java b/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/commands/CreateCommand.java
new file mode 100644
index 0000000..280534b
--- /dev/null
+++ b/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/commands/CreateCommand.java
@@ -0,0 +1,64 @@
+package org.keycloak.client.registration.cli.commands;
+
+import org.jboss.aesh.cl.Arguments;
+import org.jboss.aesh.cl.CommandDefinition;
+import org.jboss.aesh.cl.Option;
+import org.jboss.aesh.console.command.Command;
+import org.jboss.aesh.console.command.CommandResult;
+import org.jboss.aesh.console.command.invocation.CommandInvocation;
+import org.jboss.aesh.io.Resource;
+import org.keycloak.client.registration.ClientRegistrationException;
+import org.keycloak.client.registration.cli.Context;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.util.JsonSerialization;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+@CommandDefinition(name="create", description = "[OPTIONS] FILE")
+public class CreateCommand implements Command {
+
+    @Option(shortName = 'h', hasValue = false, description = "display this help and exit")
+    private boolean help;
+
+    @Arguments(description = "files or directories thats listed")
+    private List<Resource> arguments;
+
+    private Context context;
+
+    public CreateCommand(Context context) {
+        this.context = context;
+    }
+
+    @Override
+    public CommandResult execute(CommandInvocation commandInvocation) throws IOException, InterruptedException {
+        System.out.println(help);
+
+
+        if(help) {
+            commandInvocation.getShell().out().println(commandInvocation.getHelpInfo("create"));
+        }
+        else {
+
+            if(arguments != null) {
+                for(Resource f : arguments) {
+                    System.out.println(f.getAbsolutePath());
+                    ClientRepresentation rep = JsonSerialization.readValue(f.read(), ClientRepresentation.class);
+                    try {
+                        context.getReg().create(rep);
+                    } catch (ClientRegistrationException e) {
+                        e.printStackTrace();
+                    }
+                }
+
+            }
+        }
+//            reg.create();
+
+        return CommandResult.SUCCESS;
+    }
+
+}
diff --git a/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/commands/ExitCommand.java b/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/commands/ExitCommand.java
new file mode 100644
index 0000000..507881b
--- /dev/null
+++ b/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/commands/ExitCommand.java
@@ -0,0 +1,29 @@
+package org.keycloak.client.registration.cli.commands;
+
+import org.jboss.aesh.cl.CommandDefinition;
+import org.jboss.aesh.console.command.Command;
+import org.jboss.aesh.console.command.CommandResult;
+import org.jboss.aesh.console.command.invocation.CommandInvocation;
+import org.keycloak.client.registration.cli.Context;
+
+import java.io.IOException;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+@CommandDefinition(name="exit", description = "Exit the program")
+public class ExitCommand implements Command {
+
+    private Context context;
+
+    public ExitCommand(Context context) {
+        this.context = context;
+    }
+
+    @Override
+    public CommandResult execute(CommandInvocation commandInvocation) throws IOException, InterruptedException {
+        commandInvocation.stop();
+        return CommandResult.SUCCESS;
+    }
+
+}
diff --git a/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/commands/SetupCommand.java b/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/commands/SetupCommand.java
new file mode 100644
index 0000000..26579ab
--- /dev/null
+++ b/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/commands/SetupCommand.java
@@ -0,0 +1,48 @@
+package org.keycloak.client.registration.cli.commands;
+
+import org.jboss.aesh.cl.CommandDefinition;
+import org.jboss.aesh.cl.Option;
+import org.jboss.aesh.console.command.Command;
+import org.jboss.aesh.console.command.CommandResult;
+import org.jboss.aesh.console.command.invocation.CommandInvocation;
+import org.jboss.aesh.io.Resource;
+import org.keycloak.client.registration.ClientRegistrationException;
+import org.keycloak.client.registration.cli.Context;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.util.JsonSerialization;
+
+import java.io.IOException;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+@CommandDefinition(name="setup", description = "")
+public class SetupCommand implements Command {
+
+    @Option(shortName = 'h', hasValue = false, description = "display this help and exit")
+    private boolean help;
+
+    private Context context;
+
+    public SetupCommand(Context context) {
+        this.context = context;
+    }
+
+    @Override
+    public CommandResult execute(CommandInvocation commandInvocation) throws IOException, InterruptedException {
+        System.out.println(help);
+
+        if(help) {
+            commandInvocation.getShell().out().println(commandInvocation.getHelpInfo("create"));
+        }
+
+        return CommandResult.SUCCESS;
+    }
+
+
+    private String promptForUsername(CommandInvocation invocation) throws InterruptedException {
+        invocation.print("username: ");
+        return invocation.getInputLine();
+    }
+
+}
diff --git a/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/Context.java b/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/Context.java
new file mode 100644
index 0000000..49d8fb9
--- /dev/null
+++ b/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/Context.java
@@ -0,0 +1,37 @@
+package org.keycloak.client.registration.cli;
+
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.SerializationConfig;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.keycloak.client.registration.ClientRegistration;
+import org.keycloak.util.SystemPropertiesJsonParserFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class Context {
+
+    private static final ObjectMapper mapper = new ObjectMapper(new SystemPropertiesJsonParserFactory());
+    static {
+        mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
+        mapper.enable(SerializationConfig.Feature.INDENT_OUTPUT);
+    }
+
+    private ClientRegistration reg;
+
+    public ClientRegistration getReg() {
+        return reg;
+    }
+
+    public void setReg(ClientRegistration reg) {
+        this.reg = reg;
+    }
+
+    public static <T> T readJson(InputStream bytes, Class<T> type) throws IOException {
+        return mapper.readValue(bytes, type);
+    }
+
+}
diff --git a/client-registration/pom.xml b/client-registration/pom.xml
new file mode 100755
index 0000000..9d1ea9f
--- /dev/null
+++ b/client-registration/pom.xml
@@ -0,0 +1,19 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+        <artifactId>keycloak-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.7.0.Final-SNAPSHOT</version>
+    </parent>
+
+    <name>Keycloak Client Registration Parent</name>
+    <description/>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>keycloak-client-registration-parent</artifactId>
+    <packaging>pom</packaging>
+
+    <modules>
+        <module>api</module>
+        <!--<module>cli</module>-->
+    </modules>
+</project>
diff --git a/common/src/main/java/org/keycloak/common/util/ObjectUtil.java b/common/src/main/java/org/keycloak/common/util/ObjectUtil.java
index cec9ea9..bec8acf 100644
--- a/common/src/main/java/org/keycloak/common/util/ObjectUtil.java
+++ b/common/src/main/java/org/keycloak/common/util/ObjectUtil.java
@@ -13,7 +13,7 @@ public class ObjectUtil {
      * @param str2
      * @return true if both strings are null or equal
      */
-    public static boolean isEqualOrNull(Object str1, Object str2) {
+    public static boolean isEqualOrBothNull(Object str1, Object str2) {
         if (str1 == null && str2 == null) {
             return true;
         }
@@ -24,4 +24,8 @@ public class ObjectUtil {
 
         return str1.equals(str2);
     }
+
+    public static String capitalize(String str) {
+        return str.substring(0, 1).toUpperCase() + str.substring(1);
+    }
 }
diff --git a/connections/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java b/connections/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java
index e011e11..d5bfdcb 100755
--- a/connections/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java
+++ b/connections/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java
@@ -1,18 +1,5 @@
 package org.keycloak.connections.jpa;
 
-import org.hibernate.ejb.AvailableSettings;
-import org.jboss.logging.Logger;
-import org.keycloak.Config;
-import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
-import org.keycloak.connections.jpa.util.JpaUtils;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.KeycloakSessionFactory;
-
-import javax.naming.InitialContext;
-import javax.persistence.EntityManager;
-import javax.persistence.EntityManagerFactory;
-import javax.persistence.Persistence;
-import javax.sql.DataSource;
 import java.sql.Connection;
 import java.sql.DatabaseMetaData;
 import java.sql.DriverManager;
@@ -22,10 +9,25 @@ import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
+import javax.naming.InitialContext;
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.Persistence;
+import javax.sql.DataSource;
+
+import org.hibernate.ejb.AvailableSettings;
+import org.jboss.logging.Logger;
+import org.keycloak.Config;
+import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
+import org.keycloak.connections.jpa.util.JpaUtils;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.provider.ServerInfoAwareProviderFactory;
+
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class DefaultJpaConnectionProviderFactory implements JpaConnectionProviderFactory {
+public class DefaultJpaConnectionProviderFactory implements JpaConnectionProviderFactory, ServerInfoAwareProviderFactory {
 
     private static final Logger logger = Logger.getLogger(DefaultJpaConnectionProviderFactory.class);
 
diff --git a/connections/jpa/src/main/java/org/keycloak/connections/jpa/JpaConnectionProviderFactory.java b/connections/jpa/src/main/java/org/keycloak/connections/jpa/JpaConnectionProviderFactory.java
index 288e403..2fc645d 100644
--- a/connections/jpa/src/main/java/org/keycloak/connections/jpa/JpaConnectionProviderFactory.java
+++ b/connections/jpa/src/main/java/org/keycloak/connections/jpa/JpaConnectionProviderFactory.java
@@ -1,10 +1,10 @@
 package org.keycloak.connections.jpa;
 
-import org.keycloak.provider.ServerInfoAwareProviderFactory;
+import org.keycloak.provider.ProviderFactory;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public interface JpaConnectionProviderFactory extends ServerInfoAwareProviderFactory<JpaConnectionProvider> {
+public interface JpaConnectionProviderFactory extends ProviderFactory<JpaConnectionProvider> {
 
 }
diff --git a/connections/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java b/connections/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java
index 401cf74..a5429e4 100755
--- a/connections/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java
+++ b/connections/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java
@@ -12,7 +12,7 @@ public interface JpaUpdaterProvider extends Provider {
 
     public String FIRST_VERSION = "1.0.0.Final";
 
-    public String LAST_VERSION = "1.6.1";
+    public String LAST_VERSION = "1.7.0";
 
     public String getCurrentVersionSql(String defaultSchema);
 
diff --git a/connections/jpa/src/main/resources/META-INF/persistence.xml b/connections/jpa/src/main/resources/META-INF/persistence.xml
index b8592a3..e415488 100755
--- a/connections/jpa/src/main/resources/META-INF/persistence.xml
+++ b/connections/jpa/src/main/resources/META-INF/persistence.xml
@@ -31,6 +31,10 @@
         <class>org.keycloak.models.jpa.entities.RequiredActionProviderEntity</class>
         <class>org.keycloak.models.jpa.session.PersistentUserSessionEntity</class>
         <class>org.keycloak.models.jpa.session.PersistentClientSessionEntity</class>
+        <class>org.keycloak.models.jpa.entities.GroupEntity</class>
+        <class>org.keycloak.models.jpa.entities.GroupAttributeEntity</class>
+        <class>org.keycloak.models.jpa.entities.GroupRoleMappingEntity</class>
+        <class>org.keycloak.models.jpa.entities.UserGroupMembershipEntity</class>
 
         <!-- JpaAuditProviders -->
         <class>org.keycloak.events.jpa.EventEntity</class>
diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml
new file mode 100755
index 0000000..c81b062
--- /dev/null
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
+    <changeSet author="bburke@redhat.com" id="1.7.0">
+        <createTable tableName="KEYCLOAK_GROUP">
+            <column name="ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="NAME" type="VARCHAR(255)"/>
+            <column name="PARENT_GROUP" type="VARCHAR(36)"/>
+            <column name="REALM_ID" type="VARCHAR(36)"/>
+        </createTable>
+        <createTable tableName="GROUP_ROLE_MAPPING">
+            <column name="ROLE_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="GROUP_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+        </createTable>
+        <createTable tableName="GROUP_ATTRIBUTE">
+            <column name="ID" type="VARCHAR(36)" defaultValue="sybase-needs-something-here">
+                <constraints nullable="false"/>
+            </column>
+            <column name="NAME" type="VARCHAR(255)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="VALUE" type="VARCHAR(255)"/>
+            <column name="GROUP_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+        </createTable>
+        <createTable tableName="USER_GROUP_MEMBERSHIP">
+            <column name="GROUP_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="USER_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+        </createTable>
+        <createTable tableName="REALM_DEFAULT_GROUPS">
+            <column name="REALM_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="GROUP_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+        </createTable>
+
+        <addColumn tableName="IDENTITY_PROVIDER">
+            <column name="FIRST_BROKER_LOGIN_FLOW_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+        </addColumn>
+        <dropColumn tableName="IDENTITY_PROVIDER" columnName="UPDATE_PROFILE_FIRST_LGN_MD"/>
+
+        <addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_GROUP" tableName="KEYCLOAK_GROUP"/>
+        <addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="KEYCLOAK_GROUP" constraintName="FK_GROUP_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
+
+        <addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_GROUP_ATTRIBUTE_PK" tableName="GROUP_ATTRIBUTE"/>
+        <addForeignKeyConstraint baseColumnNames="GROUP_ID" baseTableName="GROUP_ATTRIBUTE" constraintName="FK_GROUP_ATTRIBUTE_GROUP" referencedColumnNames="ID" referencedTableName="KEYCLOAK_GROUP"/>
+
+        <addPrimaryKey columnNames="GROUP_ID, USER_ID" constraintName="CONSTRAINT_USER_GROUP" tableName="USER_GROUP_MEMBERSHIP"/>
+        <addForeignKeyConstraint baseColumnNames="USER_ID" baseTableName="USER_GROUP_MEMBERSHIP" constraintName="FK_USER_GROUP_USER" referencedColumnNames="ID" referencedTableName="USER_ENTITY"/>
+
+        <addPrimaryKey columnNames="ROLE_ID, GROUP_ID" constraintName="CONSTRAINT_GROUP_ROLE" tableName="GROUP_ROLE_MAPPING"/>
+        <addForeignKeyConstraint baseColumnNames="GROUP_ID" baseTableName="GROUP_ROLE_MAPPING" constraintName="FK_GROUP_ROLE_GROUP" referencedColumnNames="ID" referencedTableName="KEYCLOAK_GROUP"/>
+        <addForeignKeyConstraint baseColumnNames="ROLE_ID" baseTableName="GROUP_ROLE_MAPPING" constraintName="FK_GROUP_ROLE_ROLE" referencedColumnNames="ID" referencedTableName="KEYCLOAK_ROLE"/>
+
+        <addUniqueConstraint columnNames="GROUP_ID" constraintName="CON_GROUP_ID_DEF_GROUPS" tableName="REALM_DEFAULT_GROUPS"/>
+        <addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_DEFAULT_GROUPS" constraintName="FK_DEF_GROUPS_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
+        <addForeignKeyConstraint baseColumnNames="GROUP_ID" baseTableName="REALM_DEFAULT_GROUPS" constraintName="FK_DEF_GROUPS_GROUP" referencedColumnNames="ID" referencedTableName="KEYCLOAK_GROUP"/>
+        <addColumn tableName="CLIENT">
+            <column name="REGISTRATION_TOKEN" type="VARCHAR(255)"/>
+        </addColumn>
+
+        <modifyDataType tableName="REALM" columnName="PASSWORD_POLICY" newDataType="VARCHAR(2550)"/>
+
+    </changeSet>
+</databaseChangeLog>
\ No newline at end of file
diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-master.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-master.xml
index 3010118..2acc0bb 100755
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-master.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-master.xml
@@ -10,4 +10,5 @@
     <include file="META-INF/jpa-changelog-1.4.0.xml"/>
     <include file="META-INF/jpa-changelog-1.5.0.xml"/>
     <include file="META-INF/jpa-changelog-1.6.1.xml"/>
+    <include file="META-INF/jpa-changelog-1.7.0.xml"/>
 </databaseChangeLog>
diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java
index 00f3386..67cb127 100755
--- a/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java
+++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java
@@ -1,11 +1,12 @@
 package org.keycloak.connections.mongo;
 
-import com.mongodb.DB;
-import com.mongodb.MongoClient;
-import com.mongodb.MongoClientOptions;
-import com.mongodb.MongoClientURI;
-import com.mongodb.MongoCredential;
-import com.mongodb.ServerAddress;
+import java.lang.reflect.Method;
+import java.net.UnknownHostException;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.net.ssl.SSLSocketFactory;
 
 import org.jboss.logging.Logger;
 import org.keycloak.Config;
@@ -15,24 +16,26 @@ import org.keycloak.connections.mongo.impl.context.TransactionMongoStoreInvocati
 import org.keycloak.connections.mongo.updater.MongoUpdaterProvider;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.provider.ServerInfoAwareProviderFactory;
 
-import javax.net.ssl.SSLSocketFactory;
-import java.lang.reflect.Method;
-import java.net.UnknownHostException;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.Map;
+import com.mongodb.DB;
+import com.mongodb.MongoClient;
+import com.mongodb.MongoClientOptions;
+import com.mongodb.MongoClientURI;
+import com.mongodb.MongoCredential;
+import com.mongodb.ServerAddress;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class DefaultMongoConnectionFactoryProvider implements MongoConnectionProviderFactory {
+public class DefaultMongoConnectionFactoryProvider implements MongoConnectionProviderFactory, ServerInfoAwareProviderFactory {
 
     // TODO Make it dynamic
     private String[] entities = new String[]{
             "org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity",
             "org.keycloak.models.mongo.keycloak.entities.MongoUserEntity",
             "org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity",
+            "org.keycloak.models.mongo.keycloak.entities.MongoGroupEntity",
             "org.keycloak.models.mongo.keycloak.entities.MongoClientEntity",
             "org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity",
             "org.keycloak.models.mongo.keycloak.entities.MongoMigrationModelEntity",
diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/MongoConnectionProviderFactory.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/MongoConnectionProviderFactory.java
index bce5fe4..e787ce6 100644
--- a/connections/mongo/src/main/java/org/keycloak/connections/mongo/MongoConnectionProviderFactory.java
+++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/MongoConnectionProviderFactory.java
@@ -1,9 +1,9 @@
 package org.keycloak.connections.mongo;
 
-import org.keycloak.provider.ServerInfoAwareProviderFactory;
+import org.keycloak.provider.ProviderFactory;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public interface MongoConnectionProviderFactory extends ServerInfoAwareProviderFactory<MongoConnectionProvider> {
+public interface MongoConnectionProviderFactory extends ProviderFactory<MongoConnectionProvider> {
 }
diff --git a/connections/pom.xml b/connections/pom.xml
index 891cd59..e74e99c 100755
--- a/connections/pom.xml
+++ b/connections/pom.xml
@@ -17,7 +17,6 @@
         <module>jpa-liquibase</module>
         <module>infinispan</module>
         <module>mongo</module>
-        <module>file</module>
         <module>mongo-update</module>
         <module>http-client</module>
     </modules>
diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientInitialAccessCreatePresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientInitialAccessCreatePresentation.java
new file mode 100644
index 0000000..4c18b3d
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/ClientInitialAccessCreatePresentation.java
@@ -0,0 +1,36 @@
+package org.keycloak.representations.idm;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientInitialAccessCreatePresentation {
+
+    private Integer expiration;
+
+    private Integer count;
+
+    public ClientInitialAccessCreatePresentation() {
+    }
+
+    public ClientInitialAccessCreatePresentation(Integer expiration, Integer count) {
+        this.expiration = expiration;
+        this.count = count;
+    }
+
+    public Integer getExpiration() {
+        return expiration;
+    }
+
+    public void setExpiration(Integer expiration) {
+        this.expiration = expiration;
+    }
+
+    public Integer getCount() {
+        return count;
+    }
+
+    public void setCount(Integer count) {
+        this.count = count;
+    }
+
+}
diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientInitialAccessPresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientInitialAccessPresentation.java
new file mode 100644
index 0000000..d8021ad
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/ClientInitialAccessPresentation.java
@@ -0,0 +1,67 @@
+package org.keycloak.representations.idm;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientInitialAccessPresentation {
+
+    private String id;
+
+    private String token;
+
+    private Integer timestamp;
+
+    private Integer expiration;
+
+    private Integer count;
+
+    private Integer remainingCount;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getToken() {
+        return token;
+    }
+
+    public void setToken(String token) {
+        this.token = token;
+    }
+
+    public Integer getTimestamp() {
+        return timestamp;
+    }
+
+    public void setTimestamp(Integer timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    public Integer getExpiration() {
+        return expiration;
+    }
+
+    public void setExpiration(Integer expiration) {
+        this.expiration = expiration;
+    }
+
+    public Integer getCount() {
+        return count;
+    }
+
+    public void setCount(Integer count) {
+        this.count = count;
+    }
+
+    public Integer getRemainingCount() {
+        return remainingCount;
+    }
+
+    public void setRemainingCount(Integer remainingCount) {
+        this.remainingCount = remainingCount;
+    }
+}
diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java
index 0999505..514c0fb 100755
--- a/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java
@@ -19,6 +19,7 @@ public class ClientRepresentation {
     protected Boolean enabled;
     protected String clientAuthenticatorType;
     protected String secret;
+    protected String registrationAccessToken;
     protected String[] defaultRoles;
     protected List<String> redirectUris;
     protected List<String> webOrigins;
@@ -124,6 +125,14 @@ public class ClientRepresentation {
         this.secret = secret;
     }
 
+    public String getRegistrationAccessToken() {
+        return registrationAccessToken;
+    }
+
+    public void setRegistrationAccessToken(String registrationAccessToken) {
+        this.registrationAccessToken = registrationAccessToken;
+    }
+
     public List<String> getRedirectUris() {
         return redirectUris;
     }
@@ -251,4 +260,5 @@ public class ClientRepresentation {
     public void setProtocolMappers(List<ProtocolMapperRepresentation> protocolMappers) {
         this.protocolMappers = protocolMappers;
     }
+
 }
diff --git a/core/src/main/java/org/keycloak/representations/idm/GroupRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/GroupRepresentation.java
new file mode 100755
index 0000000..4414f24
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/GroupRepresentation.java
@@ -0,0 +1,85 @@
+package org.keycloak.representations.idm;
+
+import org.codehaus.jackson.annotate.JsonIgnore;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class GroupRepresentation {
+    protected String id;
+    protected String name;
+    protected String path;
+    protected Map<String, List<String>>  attributes;
+    protected List<String> realmRoles;
+    protected Map<String, List<String>> clientRoles;
+    protected List<GroupRepresentation> subGroups;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    public void setPath(String path) {
+        this.path = path;
+    }
+
+    public List<String> getRealmRoles() {
+        return realmRoles;
+    }
+
+    public void setRealmRoles(List<String> realmRoles) {
+        this.realmRoles = realmRoles;
+    }
+
+    public Map<String, List<String>> getClientRoles() {
+        return clientRoles;
+    }
+
+    public void setClientRoles(Map<String, List<String>> clientRoles) {
+        this.clientRoles = clientRoles;
+    }
+
+
+    public Map<String, List<String>> getAttributes() {
+        return attributes;
+    }
+
+    public void setAttributes(Map<String, List<String>>  attributes) {
+        this.attributes = attributes;
+    }
+
+    public GroupRepresentation singleAttribute(String name, String value) {
+        if (this.attributes == null) attributes = new HashMap<>();
+        attributes.put(name, Arrays.asList(value));
+        return this;
+    }
+
+    public List<GroupRepresentation> getSubGroups() {
+        return subGroups;
+    }
+
+    public void setSubGroups(List<GroupRepresentation> subGroups) {
+        this.subGroups = subGroups;
+    }
+}
diff --git a/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java
index 1e74002..0dc7f0a 100755
--- a/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java
@@ -46,12 +46,14 @@ public class IdentityProviderRepresentation {
      * @see #UPFLM_MISSING
      * @see #UPFLM_OFF
      */
+    @Deprecated
     protected String updateProfileFirstLoginMode = UPFLM_ON;
 
     protected boolean trustEmail;
     protected boolean storeToken;
     protected boolean addReadTokenRoleOnCreate;
     protected boolean authenticateByDefault;
+    protected String firstBrokerLoginFlowAlias;
     protected Map<String, String> config = new HashMap<String, String>();
 
     public String getInternalId() {
@@ -106,15 +108,17 @@ public class IdentityProviderRepresentation {
     }
 
     /**
-     * @return see {@link #updateProfileFirstLoginMode}
+     * @deprecated deprecated and replaced by configuration on IdpReviewProfileAuthenticator
      */
+    @Deprecated
     public String getUpdateProfileFirstLoginMode() {
         return updateProfileFirstLoginMode;
     }
 
     /**
-     * @param updateProfileFirstLoginMode see {@link #updateProfileFirstLoginMode}
+     * @deprecated deprecated and replaced by configuration on IdpReviewProfileAuthenticator
      */
+    @Deprecated
     public void setUpdateProfileFirstLoginMode(String updateProfileFirstLoginMode) {
         this.updateProfileFirstLoginMode = updateProfileFirstLoginMode;
     }
@@ -127,6 +131,14 @@ public class IdentityProviderRepresentation {
         this.authenticateByDefault = authenticateByDefault;
     }
 
+    public String getFirstBrokerLoginFlowAlias() {
+        return firstBrokerLoginFlowAlias;
+    }
+
+    public void setFirstBrokerLoginFlowAlias(String firstBrokerLoginFlowAlias) {
+        this.firstBrokerLoginFlowAlias = firstBrokerLoginFlowAlias;
+    }
+
     public boolean isStoreToken() {
         return this.storeToken;
     }
diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
index 387ab7e..00785cf 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -47,7 +47,9 @@ public class RealmRepresentation {
     protected String certificate;
     protected String codeSecret;
     protected RolesRepresentation roles;
+    protected List<GroupRepresentation> groups;
     protected List<String> defaultRoles;
+    protected List<String> defaultGroups;
     @Deprecated
     protected Set<String> requiredCredentials;
     protected String passwordPolicy;
@@ -268,6 +270,14 @@ public class RealmRepresentation {
         this.defaultRoles = defaultRoles;
     }
 
+    public List<String> getDefaultGroups() {
+        return defaultGroups;
+    }
+
+    public void setDefaultGroups(List<String> defaultGroups) {
+        this.defaultGroups = defaultGroups;
+    }
+
     public String getPrivateKey() {
         return privateKey;
     }
@@ -775,4 +785,12 @@ public class RealmRepresentation {
     public void setClientAuthenticationFlow(String clientAuthenticationFlow) {
         this.clientAuthenticationFlow = clientAuthenticationFlow;
     }
+
+    public List<GroupRepresentation> getGroups() {
+        return groups;
+    }
+
+    public void setGroups(List<GroupRepresentation> groups) {
+        this.groups = groups;
+    }
 }
diff --git a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
index 07865db..8635014 100755
--- a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
@@ -40,6 +40,8 @@ public class UserRepresentation {
     @Deprecated
     protected List<SocialLinkRepresentation> socialLinks;
 
+    protected List<String> groups;
+
     public String getSelf() {
         return self;
     }
@@ -216,4 +218,12 @@ public class UserRepresentation {
     public void setServiceAccountClientId(String serviceAccountClientId) {
         this.serviceAccountClientId = serviceAccountClientId;
     }
+
+    public List<String> getGroups() {
+        return groups;
+    }
+
+    public void setGroups(List<String> groups) {
+        this.groups = groups;
+    }
 }
diff --git a/core/src/main/java/org/keycloak/representations/oidc/OIDCClientRepresentation.java b/core/src/main/java/org/keycloak/representations/oidc/OIDCClientRepresentation.java
new file mode 100644
index 0000000..1ff32fc
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/oidc/OIDCClientRepresentation.java
@@ -0,0 +1,223 @@
+package org.keycloak.representations.oidc;
+
+import org.codehaus.jackson.annotate.JsonAutoDetect;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
+public class OIDCClientRepresentation {
+
+    private List<String> redirect_uris;
+
+    private String token_endpoint_auth_method;
+
+    private String grant_types;
+
+    private String response_types;
+
+    private String client_id;
+
+    private String client_secret;
+
+    private String client_name;
+
+    private String client_uri;
+
+    private String logo_uri;
+
+    private String scope;
+
+    private String contacts;
+
+    private String tos_uri;
+
+    private String policy_uri;
+
+    private String jwks_uri;
+
+    private String jwks;
+
+    private String software_id;
+
+    private String software_version;
+
+    private Integer client_id_issued_at;
+
+    private Integer client_secret_expires_at;
+
+    private String registration_client_uri;
+
+    private String registration_access_token;
+
+    public List<String> getRedirectUris() {
+        return redirect_uris;
+    }
+
+    public void setRedirectUris(List<String> redirectUris) {
+        this.redirect_uris = redirectUris;
+    }
+
+    public String getTokenEndpointAuthMethod() {
+        return token_endpoint_auth_method;
+    }
+
+    public void setTokenEndpointAuthMethod(String token_endpoint_auth_method) {
+        this.token_endpoint_auth_method = token_endpoint_auth_method;
+    }
+
+    public String getGrantTypes() {
+        return grant_types;
+    }
+
+    public void setGrantTypes(String grantTypes) {
+        this.grant_types = grantTypes;
+    }
+
+    public String getResponseTypes() {
+        return response_types;
+    }
+
+    public void setResponseTypes(String responseTypes) {
+        this.response_types = responseTypes;
+    }
+
+    public String getClientId() {
+        return client_id;
+    }
+
+    public void setClientId(String clientId) {
+        this.client_id = clientId;
+    }
+
+    public String getClientSecret() {
+        return client_secret;
+    }
+
+    public void setClientSecret(String clientSecret) {
+        this.client_secret = clientSecret;
+    }
+
+    public String getClientName() {
+        return client_name;
+    }
+
+    public void setClientName(String client_name) {
+        this.client_name = client_name;
+    }
+
+    public String getClientUri() {
+        return client_uri;
+    }
+
+    public void setClientUri(String client_uri) {
+        this.client_uri = client_uri;
+    }
+
+    public String getLogoUri() {
+        return logo_uri;
+    }
+
+    public void setLogoUri(String logo_uri) {
+        this.logo_uri = logo_uri;
+    }
+
+    public String getScope() {
+        return scope;
+    }
+
+    public void setScope(String scope) {
+        this.scope = scope;
+    }
+
+    public String getContacts() {
+        return contacts;
+    }
+
+    public void setContacts(String contacts) {
+        this.contacts = contacts;
+    }
+
+    public String getTosUri() {
+        return tos_uri;
+    }
+
+    public void setTosUri(String tos_uri) {
+        this.tos_uri = tos_uri;
+    }
+
+    public String getPolicyUri() {
+        return policy_uri;
+    }
+
+    public void setPolicyUri(String policy_uri) {
+        this.policy_uri = policy_uri;
+    }
+
+    public String getJwksUri() {
+        return jwks_uri;
+    }
+
+    public void setJwksUri(String jwks_uri) {
+        this.jwks_uri = jwks_uri;
+    }
+
+    public String getJwks() {
+        return jwks;
+    }
+
+    public void setJwks(String jwks) {
+        this.jwks = jwks;
+    }
+
+    public String getSoftwareId() {
+        return software_id;
+    }
+
+    public void setSoftwareId(String softwareId) {
+        this.software_id = softwareId;
+    }
+
+    public String getSoftwareVersion() {
+        return software_version;
+    }
+
+    public void setSoftwareVersion(String softwareVersion) {
+        this.software_version = softwareVersion;
+    }
+
+    public Integer getClientIdIssuedAt() {
+        return client_id_issued_at;
+    }
+
+    public void setClientIdIssuedAt(Integer clientIdIssuedAt) {
+        this.client_id_issued_at = clientIdIssuedAt;
+    }
+
+    public Integer getClientSecretExpiresAt() {
+        return client_secret_expires_at;
+    }
+
+    public void setClientSecretExpiresAt(Integer client_secret_expires_at) {
+        this.client_secret_expires_at = client_secret_expires_at;
+    }
+
+    public String getRegistrationClientUri() {
+        return registration_client_uri;
+    }
+
+    public void setRegistrationClientUri(String registrationClientUri) {
+        this.registration_client_uri = registrationClientUri;
+    }
+
+    public String getRegistrationAccessToken() {
+        return registration_access_token;
+    }
+
+    public void setRegistrationAccessToken(String registrationAccessToken) {
+        this.registration_access_token = registrationAccessToken;
+    }
+
+}
diff --git a/core/src/main/java/org/keycloak/util/TokenUtil.java b/core/src/main/java/org/keycloak/util/TokenUtil.java
index 0d103a6..10b6c20 100644
--- a/core/src/main/java/org/keycloak/util/TokenUtil.java
+++ b/core/src/main/java/org/keycloak/util/TokenUtil.java
@@ -19,6 +19,7 @@ public class TokenUtil {
 
     public static final String TOKEN_TYPE_OFFLINE = "Offline";
 
+
     public static boolean isOfflineTokenRequested(String scopeParam) {
         if (scopeParam == null) {
             return false;
diff --git a/dependencies/server-all/pom.xml b/dependencies/server-all/pom.xml
index 81ce095..ad786a3 100755
--- a/dependencies/server-all/pom.xml
+++ b/dependencies/server-all/pom.xml
@@ -38,10 +38,6 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
-            <artifactId>keycloak-model-file</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-model-sessions-infinispan</artifactId>
         </dependency>
         <dependency>
diff --git a/distribution/docs-dist/assembly.xml b/distribution/docs-dist/assembly.xml
index 00862c2..7da1a5c 100755
--- a/distribution/docs-dist/assembly.xml
+++ b/distribution/docs-dist/assembly.xml
@@ -9,7 +9,7 @@
 
     <fileSets>
         <fileSet>
-            <directory>../../target/site/apidocs</directory>
+            <directory>target/site/apidocs</directory>
             <outputDirectory>javadocs</outputDirectory>
         </fileSet>
         <fileSet>
diff --git a/distribution/docs-dist/src/index.html b/distribution/docs-dist/src/index.html
index f196cd5..6aeffbe 100755
--- a/distribution/docs-dist/src/index.html
+++ b/distribution/docs-dist/src/index.html
@@ -1,11 +1,33 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <style>
+        td { padding: 5px; }
+    </style>
+</head>
+
+<body>
 <h1>Keyloak Documentation</h1>
-<ul>
-    <li><a href="userguide/keycloak-server/html/index.html">Server and Keycloak Adapter Userguide HTML</a></li>
-    <li><a href="userguide/keycloak-server/html_single/index.html">Server and Keycloak Adapter Userguide HTML Single Page</a></li>
-    <li><a href="userguide/keycloak-server/pdf/keycloak-reference-guide-en-US.pdf">Server and Keycloak Adapter Userguide PDF</a></li>
-    <li><a href="userguide/saml-client-adapter/html/index.html">SAML Client Adapter Userguide HTML</a></li>
-    <li><a href="userguide/saml-client-adapter/html_single/index.html">>SAML Client Adapter Userguide HTML Single Page</a></li>
-    <li><a href="userguide/saml-client-adapter/pdf/keycloak-reference-guide-en-US.pdf">SAML Client Adapter Userguide PDF</a></li>
-    <li><a href="rest-api/overview-index.html">Admin REST API</a></li>
-    <li><a href="javadocs/index.html">Javadocs</a></li>
-</ul>
\ No newline at end of file
+<table>
+    <tr>
+        <td>Server and Keycloak Adapter Userguide</td>
+        <td><a href="userguide/keycloak-server/html/index.html">HTML</a></td>
+        <td><a href="userguide/keycloak-server/html_single/index.html">HTML Single Page</a></td>
+        <td><a href="userguide/keycloak-server/pdf/keycloak-reference-guide-en-US.pdf">PDF</a></td>
+    </tr>
+    <tr>
+        <td>SAML Client Adapter Userguide</td>
+        <td><a href="userguide/saml-client-adapter/html/index.html">HTML</a></td>
+        <td><a href="userguide/saml-client-adapter/html_single/index.html">HTML Single Page</a></td>
+        <td><a href="userguide/saml-client-adapter/pdf/keycloak-saml-adapter-reference-guide-en-US.pdf">PDF</a></td>
+    </tr>
+    <tr>
+        <td>Admin REST API</td>
+        <td colspan="3"><a href="rest-api/index.html">HTML</a></td>
+    </tr>
+    <tr>
+        <td>Javadocs</td>
+        <td colspan="3"><a href="javadocs/index.html">HTML</a></td>
+    </tr>
+</body>
+</html>
\ No newline at end of file
diff --git a/distribution/feature-packs/server-feature-pack/assembly.xml b/distribution/feature-packs/server-feature-pack/assembly.xml
index 1e142ae..4cd92ce 100644
--- a/distribution/feature-packs/server-feature-pack/assembly.xml
+++ b/distribution/feature-packs/server-feature-pack/assembly.xml
@@ -66,4 +66,15 @@
             </includes>
         </fileSet>
     </fileSets>
+
+    <files>
+        <file>
+            <source>src/main/resources/content/standalone/configuration/keycloak-server.json</source>
+            <outputDirectory>content/domain/servers/server-one/configuration</outputDirectory>
+        </file>
+        <file>
+            <source>src/main/resources/content/standalone/configuration/keycloak-server.json</source>
+            <outputDirectory>content/domain/servers/server-two/configuration</outputDirectory>
+        </file>
+    </files>
 </assembly>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/domain/subsystems.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/domain/subsystems.xml
index 2dde48f..582aa42 100644
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/domain/subsystems.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/domain/subsystems.xml
@@ -10,7 +10,7 @@
       <subsystem>ee.xml</subsystem>
       <subsystem>ejb3.xml</subsystem>
       <subsystem>io.xml</subsystem>
-      <subsystem>infinispan.xml</subsystem>
+      <subsystem>keycloak-infinispan.xml</subsystem>
       <subsystem>jaxrs.xml</subsystem>
       <subsystem>jca.xml</subsystem>
       <subsystem>jdr.xml</subsystem>
@@ -41,7 +41,7 @@
       <subsystem>ee.xml</subsystem>
       <subsystem supplement="ha">ejb3.xml</subsystem>
       <subsystem>io.xml</subsystem>
-      <subsystem supplement="ha">infinispan.xml</subsystem>
+      <subsystem supplement="ha">keycloak-infinispan.xml</subsystem>
       <subsystem>jaxrs.xml</subsystem>
       <subsystem>jca.xml</subsystem>
       <subsystem>jdr.xml</subsystem>
@@ -74,7 +74,7 @@
       <subsystem supplement="full">ee.xml</subsystem>
       <subsystem supplement="full">ejb3.xml</subsystem>
       <subsystem>io.xml</subsystem>
-      <subsystem>infinispan.xml</subsystem>
+      <subsystem>keycloak-infinispan.xml</subsystem>
       <subsystem>iiop-openjdk.xml</subsystem>
       <subsystem>jaxrs.xml</subsystem>
       <subsystem>jca.xml</subsystem>
@@ -108,7 +108,7 @@
       <subsystem supplement="full">ee.xml</subsystem>
       <subsystem supplement="full-ha">ejb3.xml</subsystem>
       <subsystem>io.xml</subsystem>
-      <subsystem supplement="ha">infinispan.xml</subsystem>
+      <subsystem supplement="ha">keycloak-infinispan.xml</subsystem>
       <subsystem>iiop-openjdk.xml</subsystem>
       <subsystem>jaxrs.xml</subsystem>
       <subsystem>jca.xml</subsystem>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml
index c339f44..7e61fb4 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml
@@ -8,7 +8,6 @@
             <module name="org.keycloak.keycloak-connections-jpa-liquibase" services="import"/>
             <module name="org.keycloak.keycloak-connections-mongo" services="import"/>
             <module name="org.keycloak.keycloak-connections-mongo-update" services="import"/>
-            <module name="org.keycloak.keycloak-connections-file" services="import"/>
             <module name="org.keycloak.keycloak-common" services="import"/>
             <module name="org.keycloak.keycloak-core" services="import"/>
             <module name="org.keycloak.keycloak-email-api" services="import"/>
@@ -33,7 +32,6 @@
             <module name="org.keycloak.keycloak-model-api" services="import"/>
             <module name="org.keycloak.keycloak-model-jpa" services="import"/>
             <module name="org.keycloak.keycloak-model-mongo" services="import"/>
-            <module name="org.keycloak.keycloak-model-file" services="import"/>
             <module name="org.keycloak.keycloak-model-sessions-infinispan" services="import"/>
             <module name="org.keycloak.keycloak-saml-protocol" services="import"/>
             <module name="org.keycloak.keycloak-services" export="true" services="import"/>
@@ -70,4 +68,4 @@
             <subsystem name="weld"/>
         </exclude-subsystems>
     </deployment>
-</jboss-deployment-structure>
\ No newline at end of file
+</jboss-deployment-structure>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml
index 77ce3ad..340c3bf 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml
@@ -18,7 +18,6 @@
         <module name="org.keycloak.keycloak-connections-jpa-liquibase" services="import"/>
         <module name="org.keycloak.keycloak-connections-mongo" services="import"/>
         <module name="org.keycloak.keycloak-connections-mongo-update" services="import"/>
-        <module name="org.keycloak.keycloak-connections-file" services="import"/>
         <module name="org.keycloak.keycloak-common" services="import"/>
         <module name="org.keycloak.keycloak-core" services="import"/>
         <module name="org.keycloak.keycloak-email-api" services="import"/>
@@ -43,7 +42,6 @@
         <module name="org.keycloak.keycloak-model-api" services="import"/>
         <module name="org.keycloak.keycloak-model-jpa" services="import"/>
         <module name="org.keycloak.keycloak-model-mongo" services="import"/>
-        <module name="org.keycloak.keycloak-model-file" services="import"/>
         <module name="org.keycloak.keycloak-model-sessions-infinispan" services="import"/>
         <module name="org.keycloak.keycloak-saml-core" services="import"/>
         <module name="org.keycloak.keycloak-saml-protocol" services="import"/>
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/build.xml b/distribution/server-overlay/eap6/eap6-server-modules/build.xml
index 60ccb65..9f60836 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/build.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/build.xml
@@ -173,10 +173,6 @@
             <maven-resource group="org.keycloak" artifact="keycloak-connections-jpa-liquibase"/>
         </module-def>
 
-        <module-def name="org.keycloak.keycloak-connections-file">
-            <maven-resource group="org.keycloak" artifact="keycloak-connections-file"/>
-        </module-def>
-
         <module-def name="org.keycloak.keycloak-connections-infinispan">
             <maven-resource group="org.keycloak" artifact="keycloak-connections-infinispan"/>
         </module-def>
@@ -224,11 +220,11 @@
         <module-def name="org.keycloak.keycloak-social-facebook">
             <maven-resource group="org.keycloak" artifact="keycloak-social-facebook"/>
         </module-def>
-    	
+
     	  <module-def name="org.keycloak.keycloak-social-linkedin">
     	      <maven-resource group="org.keycloak" artifact="keycloak-social-linkedin"/>
     	  </module-def>
-    	
+
       	<module-def name="org.keycloak.keycloak-social-stackoverflow">
     	      <maven-resource group="org.keycloak" artifact="keycloak-social-stackoverflow"/>
     	  </module-def>
@@ -250,12 +246,6 @@
             <maven-resource group="org.keycloak" artifact="keycloak-saml-protocol"/>
         </module-def>
 
-        <!-- file -->
-
-        <module-def name="org.keycloak.keycloak-model-file">
-            <maven-resource group="org.keycloak" artifact="keycloak-model-file"/>
-        </module-def>
-
         <!-- mongo -->
 
         <module-def name="org.keycloak.keycloak-connections-mongo">
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml
index c339f44..7e61fb4 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml
@@ -8,7 +8,6 @@
             <module name="org.keycloak.keycloak-connections-jpa-liquibase" services="import"/>
             <module name="org.keycloak.keycloak-connections-mongo" services="import"/>
             <module name="org.keycloak.keycloak-connections-mongo-update" services="import"/>
-            <module name="org.keycloak.keycloak-connections-file" services="import"/>
             <module name="org.keycloak.keycloak-common" services="import"/>
             <module name="org.keycloak.keycloak-core" services="import"/>
             <module name="org.keycloak.keycloak-email-api" services="import"/>
@@ -33,7 +32,6 @@
             <module name="org.keycloak.keycloak-model-api" services="import"/>
             <module name="org.keycloak.keycloak-model-jpa" services="import"/>
             <module name="org.keycloak.keycloak-model-mongo" services="import"/>
-            <module name="org.keycloak.keycloak-model-file" services="import"/>
             <module name="org.keycloak.keycloak-model-sessions-infinispan" services="import"/>
             <module name="org.keycloak.keycloak-saml-protocol" services="import"/>
             <module name="org.keycloak.keycloak-services" export="true" services="import"/>
@@ -70,4 +68,4 @@
             <subsystem name="weld"/>
         </exclude-subsystems>
     </deployment>
-</jboss-deployment-structure>
\ No newline at end of file
+</jboss-deployment-structure>
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml
index 44703f8..aa895e8 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml
@@ -18,7 +18,6 @@
         <module name="org.keycloak.keycloak-connections-jpa-liquibase" services="import"/>
         <module name="org.keycloak.keycloak-connections-mongo" services="import"/>
         <module name="org.keycloak.keycloak-connections-mongo-update" services="import"/>
-        <module name="org.keycloak.keycloak-connections-file" services="import"/>
         <module name="org.keycloak.keycloak-common" services="import"/>
         <module name="org.keycloak.keycloak-core" services="import"/>
         <module name="org.keycloak.keycloak-email-api" services="import"/>
@@ -43,7 +42,6 @@
         <module name="org.keycloak.keycloak-model-api" services="import"/>
         <module name="org.keycloak.keycloak-model-jpa" services="import"/>
         <module name="org.keycloak.keycloak-model-mongo" services="import"/>
-        <module name="org.keycloak.keycloak-model-file" services="import"/>
         <module name="org.keycloak.keycloak-model-sessions-infinispan" services="import"/>
 
         <module name="org.keycloak.keycloak-saml-core" services="import"/>
diff --git a/docbook/auth-server-docs/pom.xml b/docbook/auth-server-docs/pom.xml
index a7c2ddd..546b18d 100755
--- a/docbook/auth-server-docs/pom.xml
+++ b/docbook/auth-server-docs/pom.xml
@@ -114,6 +114,10 @@
                             <name>picketlink.version</name>
                             <value>${picketlink.version}</value>
                         </injection>
+                        <injection>
+                            <name>wildfly.version</name>
+                            <value>${wildfly.version}</value>
+                        </injection>
                     </injections>
                     <options>
                         <xmlTransformerType>saxon</xmlTransformerType>
diff --git a/docbook/auth-server-docs/reference/en/en-US/master.xml b/docbook/auth-server-docs/reference/en/en-US/master.xml
index 2af744f..16a8601 100755
--- a/docbook/auth-server-docs/reference/en/en-US/master.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/master.xml
@@ -27,6 +27,7 @@
                 <!ENTITY Migration SYSTEM "modules/MigrationFromOlderVersions.xml">
                 <!ENTITY Email SYSTEM "modules/email.xml">
                 <!ENTITY Roles SYSTEM "modules/roles.xml">
+                <!ENTITY Groups SYSTEM "modules/groups.xml">
                 <!ENTITY DirectAccess SYSTEM "modules/direct-access.xml">
                 <!ENTITY ServiceAccounts SYSTEM "modules/service-accounts.xml">
                 <!ENTITY CORS SYSTEM "modules/cors.xml">
@@ -48,6 +49,7 @@
                 <!ENTITY Recaptcha SYSTEM "modules/recaptcha.xml">
                 <!ENTITY AuthSPI SYSTEM "modules/auth-spi.xml">
                 <!ENTITY FilterAdapter SYSTEM "modules/servlet-filter-adapter.xml">
+                <!ENTITY ClientRegistration SYSTEM "modules/client-registration.xml">
                 ]>
 
 <book>
@@ -116,6 +118,7 @@ This one is short
         &MultiTenancy;
         &JAAS;
     </chapter>
+    &ClientRegistration;
 
     &IdentityBroker;
     &Themes;
@@ -131,6 +134,7 @@ This one is short
     </chapter>
     &AccessTypes;
     &Roles;
+    &Groups;
     &DirectAccess;
     &ServiceAccounts;
     &CORS;
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/admin-permissions.xml b/docbook/auth-server-docs/reference/en/en-US/modules/admin-permissions.xml
index 833d390..678c6d1 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/admin-permissions.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/admin-permissions.xml
@@ -64,6 +64,9 @@
                     <literal>manage-applications</literal> - Create, modify and delete applications in the realm
                 </listitem>
                 <listitem>
+                    <literal>create-clients</literal> - Create clients in the realm
+                </listitem>
+                <listitem>
                     <literal>manage-clients</literal> - Create, modify and delete clients in the realm
                 </listitem>
                 <listitem>
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/auth-spi.xml b/docbook/auth-server-docs/reference/en/en-US/modules/auth-spi.xml
index 10cb89d..12e2b1a 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/auth-spi.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/auth-spi.xml
@@ -866,6 +866,14 @@ public class SecretQuestionRequiredActionFactory implements RequiredActionFactor
         </para>
     </section>
 
+    <section>
+        <title>Modifying First Broker Login Flow</title>
+        <para>
+            First Broker Login flow is used during first login with some identity provider. Term <literal>First Login</literal> means that there is not yet existing Keycloak account
+            linked with the particular authenticated identity provider account. More details about this flow are in the <link linkend="identity-broker-first-login">Identity provider chapter</link>.
+        </para>
+    </section>
+
     <section id="client_authentication">
         <title>Authentication of clients</title>
         <para>Keycloak actually supports pluggable authentication for <ulink url="http://openid.net/specs/openid-connect-core-1_0.html">OpenID Connect</ulink>
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/client-registration.xml b/docbook/auth-server-docs/reference/en/en-US/modules/client-registration.xml
new file mode 100755
index 0000000..fab7119
--- /dev/null
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/client-registration.xml
@@ -0,0 +1,215 @@
+<chapter id="client-registration">
+    <title>Client Registration</title>
+
+    <para>
+        In order for an application or service to utilize Keycloak it has to register a client in Keycloak. An
+        admin can do this through the admin console (or admin REST endpoints), but clients can also register themselves
+        through Keycloak's client registration service.
+    </para>
+
+    <para>
+        The Client Registration Service provides built-in support for Keycloak Client Representations, OpenID Connect
+        Client Meta Data and SAML Entity Descriptors. It's also possible to plugin custom client registration providers
+        if required. The Client Registration Service endpoint is <literal>&lt;KEYCLOAK URL&gt;/realms/&lt;realm&gt;/clients/&lt;provider&gt;</literal>.
+    </para>
+    <para>
+        The built-in supported <literal>providers</literal> are:
+        <itemizedlist>
+            <listitem><literal>default</literal> Keycloak Representations</listitem>
+            <listitem><literal>install</literal> Keycloak Adapter Configuration</listitem>
+            <listitem><literal>openid-connect</literal> OpenID Connect Dynamic Client Registration</listitem>
+            <listitem><literal>saml2-entity-descriptor</literal> SAML Entity Descriptors</listitem>
+        </itemizedlist>
+        The following sections will describe how to use the different providers.
+    </para>
+
+    <section>
+        <title>Authentication</title>
+        <para>
+            To invoke the Client Registration Services you need a token. The token can be a standard bearer token, a
+            initial access token or a registration access token.
+        </para>
+
+        <section>
+            <title>Bearer Token</title>
+            <para>
+                The bearertoken can be issued on behalf of a user or a Service Account. The following permissions are required
+                to invoke the endpoints (see <link linkend='admin-permissions'>Admin Permissions</link> for more details):
+                <itemizedlist>
+                    <listitem>
+                        <literal>create-client</literal> or <literal>manage-client</literal> - To create clients
+                    </listitem>
+                    <listitem>
+                        <literal>view-client</literal> or <literal>manage-client</literal> - To view clients
+                    </listitem>
+                    <listitem>
+                        <literal>manage-client</literal> - To update or delete clients
+                    </listitem>
+                </itemizedlist>
+                If you are using a regular bearer token to create clients we recommend using a token from on behalf of a
+                Service Account with only the <literal>create-client</literal> role. See the
+                <link linkend="service-accounts">Service Account</link> section for more details.
+            </para>
+        </section>
+
+        <section>
+            <title>Initial Access Token</title>
+            <para>
+                The best approach to create new clients is by using initial access tokens. An initial access token can
+                only be used to create clients and has a configurable expiration as well as a configurable limit on
+                how many clients can be created.
+            </para>
+            <para>
+                An initial access token can be created through the admin console. To create a new initial access token
+                first select the realm in the admin console, then click on <literal>Realm Settings</literal> in the menu
+                on the left, followed by <literal>Initial Access Tokens</literal> in the tabs displayed in the page.
+            </para>
+            <para>
+                You will now be able to see any existing initial access tokens. If you have access you can delete tokens
+                that are no longer required. You can only retrieve the value of the token when you are creating it. To
+                create a new token click on <literal>Create</literal>. You can now optionally add how long the token
+                should be valid, also how many clients can be created using the token. After you click on <literal>Save</literal>
+                the token value is displayed. It is important that you copy/paste this token now as you won't be able
+                to retrieve it later. If you forget to copy/paste it, then delete the token and create another one.
+                The token value is used as a standard bearer token when invoking the Client Registration Services, by
+                adding it to the Authorization header in the request. For example:
+<programlisting><![CDATA[
+Authorization: bearer eyJhbGciOiJSUzI1NiJ9.eyJqdGkiOiJmMjJmNzQyYy04ZjNlLTQ2M....
+]]></programlisting>
+            </para>
+        </section>
+
+        <section>
+            <title>Registration Access Token</title>
+            <para>
+                When you create a client through the Client Registration Service the response will include a registration
+                access token. The registration access token provides access to retrieve the client configuration later, but
+                also to update or delete the client. The registration access token is included with the request in the
+                same way as a bearer token or initial access token. Registration access tokens are only valid once
+                when it's used the response will include a new token.
+            </para>
+            <para>
+                If a client was created outside of the Client Registration Service it won't have a registration access
+                token associated with it. You can create one through the admin console. This can also be useful if
+                you loose the token for a particular client. To create a new token find the client in the admin console
+                and click on <literal>Credentials</literal>. Then click on <literal>Generate registration access token</literal>.
+            </para>
+        </section>
+    </section>
+
+    <section>
+        <title>Keycloak Representations</title>
+        <para>
+            The <literal>default</literal> client registration provider can be used to create, retrieve, update and delete a client. It uses
+            Keycloaks Client Representation format which provides support for configuring clients exactly as they can
+            be configured through the admin console, including for example configuring protocol mappers.
+        </para>
+        <para>
+            To create a client create a Client Representation (JSON) then do a HTTP POST to:
+            <literal>&lt;KEYCLOAK URL&gt;/realms/&lt;realm&gt;/clients/&lt;provider&gt;/default</literal>. It will return a Client Representation
+            that also includes the registration access token. You should save the registration access token somewhere
+            if you want to retrieve the config, update or delete the client later.
+        </para>
+        <para>
+            To retrieve the Client Representation then do a HTTP GET to:
+            <literal>&lt;KEYCLOAK URL&gt;/realms/&lt;realm&gt;clients/&lt;provider&gt;/default/&lt;client id&gt;</literal>. It will also
+            return a new registration access token.
+        </para>
+        <para>
+            To update the Client Representation then do a HTTP PUT to with the updated Client Representation to:
+            <literal>&lt;KEYCLOAK URL&gt;/realms/&lt;realm&gt;/clients/&lt;provider&gt;/default/&lt;client id&gt;</literal>. It will also
+            return a new registration access token.
+        </para>
+        <para>
+            To delete the Client Representation then do a HTTP DELETE to:
+            <literal>&lt;KEYCLOAK URL&gt;/realms/&lt;realm&gt;/clients/&lt;provider&gt;/default/&lt;client id&gt;</literal>
+        </para>
+    </section>
+
+    <section>
+        <title>Keycloak Adapter Configuration</title>
+        <para>
+            The <literal>installation</literal> client registration provider can be used to retrieve the adapter configuration
+            for a client. In addition to token authentication you can also authenticate with client credentials using
+            HTTP basic authentication. To do this include the following header in the request:
+<programlisting><![CDATA[
+Authorization: basic BASE64(client-id + ':' + client-secret)
+]]></programlisting>
+        </para>
+        <para>
+            To retrieve the Adapter Configuration then do a HTTP GET to:
+            <literal>&lt;KEYCLOAK URL&gt;//realms/&lt;realm&gt;clients/&lt;provider&gt;/installation/&lt;client id&gt;</literal>
+        </para>
+        <para>
+            No authentication is required for public clients. This means that for the JavaScript adapter you can
+            load the client configuration directly from Keycloak using the above URL.
+        </para>
+    </section>
+
+    <section>
+        <title>OpenID Connect Dynamic Client Registration</title>
+        <para>
+            Keycloak implements <ulink url="https://openid.net/specs/openid-connect-registration-1_0.html">OpenID Connect Dynamic Client Registration</ulink>,
+            which extends <ulink url="https://tools.ietf.org/html/rfc7591">OAuth 2.0 Dynamic Client Registration Protocol</ulink> and
+            <ulink url="https://tools.ietf.org/html/rfc7592">OAuth 2.0 Dynamic Client Registration Management Protocol</ulink>.
+        </para>
+        <para>
+            The endpoint to use these specifications to register clients in Keycloak is:
+            <literal>&lt;KEYCLOAK URL&gt;/realms/&lt;realm&gt;/clients/&lt;provider&gt;/oidc[/&lt;client id&gt;]</literal>.
+        </para>
+        <para>
+            This endpoints can also be found in the OpenID Connect Discovery endpoint for the realm:
+            <literal>&lt;KEYCLOAK URL&gt;/realms/&lt;realm&gt;/.well-known/openid-configuration</literal>.
+        </para>
+    </section>
+
+    <section>
+        <title>SAML Entity Descriptors</title>
+        <para>
+            The SAML Entity Descriptor endpoint only supports using SAML v2 Entity Descriptors to create clients. It
+            doesn't support retrieving, updating or deleting clients. For those operations the Keycloak representation
+            endpoints should be used. When creating a client a Keycloak Client Representation is returned with details
+            about the created client, including a registration access token.
+        </para>
+        <para>
+            To create a client do a HTTP POST with the SAML Entity Descriptor to:
+            <literal>&lt;KEYCLOAK URL&gt;/realms/&lt;realm&gt;/clients/&lt;provider&gt;/saml2-entity-descriptor</literal>.
+        </para>
+    </section>
+
+    <section>
+        <title>Client Registration Java API</title>
+        <para>
+            The Client Registration Java API makes it easy to use the Client Registration Service using Java. To use
+            include the dependency <literal>org.keycloak:keycloak-client-registration-api:&gt;VERSION&lt;</literal> from
+            Maven.
+        </para>
+        <para>
+            For full instructions on using the Client Registration refer to the JavaDocs. Below is an example of creating
+            a client:
+<programlisting><![CDATA[
+String initialAccessToken = "eyJhbGciOiJSUzI1NiJ9.eyJqdGkiOiJmMjJmNzQyYy04ZjNlLTQ2M....";
+
+ClientRepresentation client = new ClientRepresentation();
+client.setClientId(CLIENT_ID);
+
+ClientRegistration reg = ClientRegistration.create().url("http://keycloak/auth/realms/myrealm").build();
+reg.auth(initialAccessToken);
+
+client = reg.create(client);
+
+String registrationAccessToken = client.getRegistrationAccessToken();
+]]></programlisting>
+        </para>
+    </section>
+
+    <!--
+    <section>
+        <title>Client Registration CLI</title>
+        <para>
+            TODO
+        </para>
+    </section>
+    -->
+
+</chapter>
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/custom-attributes.xml b/docbook/auth-server-docs/reference/en/en-US/modules/custom-attributes.xml
index 7aefdb6..49d58ad 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/custom-attributes.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/custom-attributes.xml
@@ -8,7 +8,7 @@
     <para>
         <orderedlist>
             <listitem>
-                Create a new theme within the <literal>themes/admin/mytheme</literal> directory in your distribution.
+                Create a new theme within the <literal>themes/mytheme/admin</literal> directory in your distribution.
                 Where <literal>mytheme</literal> is whatever you want to name your theme.
             </listitem>
             <listitem>
@@ -19,15 +19,15 @@ import=common/keycloak
 ]]></programlisting>
             </listitem>
             <listitem>
-                Copy the file <literal>themes/admin/base/resources/partials/user-attribute-entry.html</literal> into the
-                a mirror directory in your theme: <literal>themes/admin/mytheme/resources/partials/user-attribute-entry.html</literal>.
+                Copy the file <literal>themes/base/admin/resources/partials/user-attributes.html</literal> into the
+                a mirror directory in your theme: <literal>themes/mytheme/admin/resources/partials/user-attributes.html</literal>.
                 What you are doing here is overriding the user attribute entry page in the admin console and putting in
                 what attributes you want.  This file already contains an example of entering address data.  You can remove
                 this if you want and replace it with something else.  Also, if you want to edit this file directly instead
                 of creating a new theme, you can.
             </listitem>
             <listitem>
-                In the <literal>user-attribute-entry.html</literal> file add your custom user attribute entry form item.  For example
+                In the <literal>user-attributes.html</literal> file add your custom user attribute entry form item.  For example
 <programlisting><![CDATA[    <div class="form-group clearfix block">
         <label class="col-sm-2 control-label" for="mobile">Mobile</label>
         <div class="col-sm-6">
@@ -52,7 +52,7 @@ import=common/keycloak
     <para>
         <orderedlist>
             <listitem>
-                Create a new theme within the <literal>themes/login/mytheme</literal> directory in your distribution.
+                Create a new theme within the <literal>themes/mytheme/login</literal> directory in your distribution.
                 Where <literal>mytheme</literal> is whatever you want to name your theme.
             </listitem>
             <listitem>
@@ -63,8 +63,8 @@ import=common/keycloak
 styles= ../patternfly/lib/patternfly/css/patternfly.css ../patternfly/css/login.css ../patternfly/lib/zocial/zocial.css css/login.css]]></programlisting>
             </listitem>
             <listitem>
-                Copy the file <literal>themes/login/base/register.ftl</literal> into the
-                a mirror directory in your theme: <literal>themes/login/mytheme/register.ftl</literal>.
+                Copy the file <literal>themes/base/login/register.ftl</literal> into the
+                a mirror directory in your theme: <literal>themes/mytheme/login/register.ftl</literal>.
                 What you are doing here is overriding the registration page and adding
                 what attributes you want.  This file already contains an example of entering address data.  You can remove
                 this if you want and replace it with something else.  Also, if you want to edit this file directly instead
@@ -101,7 +101,7 @@ styles= ../patternfly/lib/patternfly/css/patternfly.css ../patternfly/css/login.
     <para>
         <orderedlist>
             <listitem>
-                Create a new theme within the <literal>themes/account/mytheme</literal> directory in your distribution.
+                Create a new theme within the <literal>themes/mytheme/account</literal> directory in your distribution.
                 Where <literal>mytheme</literal> is whatever you want to name your theme.
             </listitem>
             <listitem>
@@ -113,8 +113,8 @@ import=common/keycloak
 styles= ../patternfly/lib/patternfly/css/patternfly.css ../patternfly/css/account.css css/account.css]]></programlisting>
             </listitem>
             <listitem>
-                Copy the file <literal>themes/account/base/account.ftl</literal> into the
-                a mirror directory in your theme: <literal>themes/account/mytheme/account.ftl</literal>.
+                Copy the file <literal>themes/base/account/account.ftl</literal> into the
+                a mirror directory in your theme: <literal>themes/mytheme/account/account.ftl</literal>.
                 What you are doing here is overriding the profile page and adding
                 what attributes you want to manage.  This file already contains an example of entering address data.  You can remove
                 this if you want and replace it with something else.  Also, if you want to edit this file directly instead
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/groups.xml b/docbook/auth-server-docs/reference/en/en-US/modules/groups.xml
new file mode 100755
index 0000000..eb54e0e
--- /dev/null
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/groups.xml
@@ -0,0 +1,31 @@
+<chapter id="groups">
+    <title>Groups</title>
+    <para>
+        Groups in Keycloak allow you to manage a common set of attributes and role mappings for a large set of users.
+        Users can be members of zero or more groups.  Users inherit the attributes and role mappings assign to each group.
+        As an admin this makes it easy for you to manage permissions for a user in one place.
+    </para>
+    <para>
+        Groups are hierarchical.  A group can have many subgroups, but a group can only have one parent.  Subgroups inherit
+        the attributes and role mappings from the parent.  This applies to user as well.  So, if you have a parent group and a child group
+        and a user that only belongs to the child group, the user inherits the attributes and role mappings of both the
+        parent and child.
+    </para>
+    <section>
+        <title>Groups vs. Roles</title>
+        <para>
+            In the IT world the concepts of Group and Role are often blurred and interchangeable.  In Keycloak, Groups are just
+            a collection of users that you can apply roles and attributes to in one place.  Roles are used to assign permissions
+            and access control.
+        </para>
+        <para>
+            Keycloak Roles have the concept of a Composite Role.  A role can be associated with one or more additional roles.
+            This is called a Composite Role.  If a user has a role mapping to the Composite Role, they inherit all the roles associated
+            with the composite.  So what's the difference from a Keycloak Group and a Composite Role?  Logically they could be
+            used for the same exact thing.  The difference is conceptual.  Composite roles should be used to compose the
+            permission model of your set of services and applications.  So, roles become a set of permissions.  Groups on the
+            other hand, would be a set of users that have a set of permissions.  Use Groups to manage users, composite roles to
+            manage applications and services.
+        </para>
+    </section>
+</chapter>
\ No newline at end of file
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/identity-broker.xml b/docbook/auth-server-docs/reference/en/en-US/modules/identity-broker.xml
index a262b85..41c36f0 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/identity-broker.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/identity-broker.xml
@@ -66,7 +66,7 @@
 
     </itemizedlist>
 
-    <section>
+    <section id="identity-broker-overview">
         <title>Overview</title>
 
         <para>
@@ -127,10 +127,11 @@
             <listitem>
                 <para>
                     Now Keycloak is going to check if the response from the identity provider is valid. If valid, it will create an user
-                    or just skip that if the user already exists. If it is a new user, Keycloak will ask informations about the user to the identity provider
+                    or just skip that if the user already exists. If it is a new user, Keycloak may ask informations about the user to the identity provider
                     (or just read that from a security token) and create the user locally. This is what we call <emphasis>identity federation</emphasis>.
-                    If the user already exists Keycloak will ask him to link the identity returned from the identity provider
-                    with his existing account. A process that we call <emphasis>account linking</emphasis>.
+                    If the user already exists Keycloak may ask him to link the identity returned from the identity provider
+                    with his existing account. A process that we call <emphasis>account linking</emphasis>. What exactly is done is configurable
+                    and can be specified by setup of <link linkend="identity-broker-first-login">First Login Flow</link> .
                     At the end of this step, Keycloak authenticates the user and issues its own token in order to access
                     the requested resource in the service provider.
                 </para>
@@ -210,7 +211,7 @@
                     <para>
                         Social providers allows you to enable social authentication to your realm.
                         Keycloak makes it easy to let users log in to your application using an existing account with a social network.
-                        Currently Facebook, Google and Twitter are supported with more planned for the future.
+                        Currently Facebook, Google, Twitter, GitHub, LinkedIn and StackOverflow are supported with more planned for the future.
                     </para>
                 </listitem>
             </varlistentry>
@@ -274,6 +275,15 @@
                             be used by any other means.
                         </entry>
                     </row>
+                    <row>
+                        <entry>
+                            <literal>Authenticate By Default</literal>
+                        </entry>
+                        <entry>
+                            If enabled, Keycloak will automatically redirect to this identity provider even before displaying login screen.
+                            In other words, steps 3 and 4 from <link linkend="identity-broker-overview">the base flow</link> are skipped.
+                        </entry>
+                    </row>
                    <row>
                         <entry>
                             <literal>Store Tokens</literal>
@@ -295,20 +305,6 @@
                     </row>
                     <row>
                         <entry>
-                            <literal>Update Profile on First Login</literal>
-                        </entry>
-                        <entry>
-                            Allows you to force users to update their profile right after the authentication finishes and
-                            before the account is actually created in Keycloak. When "On", users will be always presented with the
-                            <emphasis>update profile page</emphasis> asking for additional information in order to federate their identities.
-                            When "On missing info", users will be presented with the <emphasis>update profile page</emphasis> only if some 
-                            mandatory information (email, first name, last name) is not provided by identity provider.
-                            If "Off", the account will be created with the minimal information obtained from the identity provider
-                            during the authentication process.
-                        </entry>
-                    </row>
-                    <row>
-                        <entry>
                             <literal>Trust email</literal>
                         </entry>
                         <entry>
@@ -326,6 +322,16 @@
                             You can put number into this field, providers with lower numbers are shown first.
                         </entry>
                     </row>
+                    <row>
+                        <entry>
+                            <literal>First Login Flow</literal>
+                        </entry>
+                        <entry>
+                            Alias of authentication flow, which is triggered during first login with this identity provider. Term <literal>First Login</literal>
+                            means that there is not yet existing Keycloak account linked with the authenticated identity provider account.
+                            More details in <link linkend="identity-broker-first-login">First Login section</link>.
+                        </entry>
+                    </row>
                 </tbody>
             </tgroup>
         </table>
@@ -340,8 +346,8 @@
             Forcing users to register to your realm when they want to access applications is hard.
             So is trying to remember yet another username and password combination.
             Social identity providers makes it easy for users to register on your realm and quickly sign in using a social network.
-            Keycloak provides built-in support for the most common social networks out there, such as Google, Facebook, Twitter and
-            even Github.
+            Keycloak provides built-in support for the most common social networks out there, such as Google, Facebook, Twitter,
+            Github, LinkedId and StackOverflow.
         </para>
 
         <section>
@@ -1211,7 +1217,13 @@ Authorization: Bearer {keycloak_access_token}]]></programlisting>
     <section>
         <title>Automatically Select and Identity Provider</title>
         <para>
-            Applications can automatically select an identity provider in order to authenticate an user. In this case, the user will not be presented to the login page but automatically redirected to the identity provider.
+            Each Identity provider has option <literal>Authenticate By Default</literal>, which allows that Identity provider is automatically
+            selected during authentication. User won't even see Keycloak login page, but is automatically redirected to the identity provider.
+        </para>
+        <para>
+            Applications can also automatically select an identity provider in order to authenticate an user.
+            Selection per application is preferred over <literal>Authenticate By Default</literal> option if you need more control
+            on when exactly is Identity provider automatically selected.
         </para>
         <para>
             Keycloak supports a specific HTTP query parameter that you can use as a hint to tell the server which identity provider should be used to authenticate the user.
@@ -1283,6 +1295,122 @@ keycloak.createLoginUrl({
         </para>
     </section>
 
+    <section id="identity-broker-first-login">
+        <title>First Login Flow</title>
+        <para>
+            When Keycloak successfully authenticates user through identity provider (step 8 in <link linkend="identity-broker-overview">Overview</link> chapter),
+            there can be two situations:
+            <orderedlist>
+                <listitem>
+                    <para>
+                        There is already Keycloak user account linked with the authenticated identity provider account. In this case,
+                        Keycloak will just authenticate as the existing user and redirect back to application (step 9 in <link linkend="identity-broker-overview">Overview</link> chapter).
+                    </para>
+                </listitem>
+                <listitem>
+                    <para>
+                        There is not yet existing Keycloak user account linked with the identity provider account. This situation is more tricky.
+                        Usually you just want to register new account into Keycloak database, but what if there is existing Keycloak account with same email like the identity provider account?
+                        Automatically link identity provider account with existing Keycloak account is not very good option as there are possible security flaws related to that...
+                    </para>
+                </listitem>
+            </orderedlist>
+        </para>
+        <para>
+            Because we had various requirements what to do in second case, we changed the behaviour to be flexible and configurable
+            through <link linkend="auth_spi">Authentication Flows SPI</link>. In admin console in Identity provider settings, there is option
+            <literal>First Login Flow</literal>, which allows you to choose, which workflow will be used after "first login" with this identity provider account.
+            By default it points to <literal>first broker login</literal> flow, but you can configure and use your own flow and use different flows for different identity providers etc.
+        </para>
+        <para>
+            The flow itself is configured in admin console under <literal>Authentication</literal> tab. When you choose <literal>First Broker Login</literal> flow,
+            you will see what authenticators are used by default. You can either re-configure existing flow (For example disable some authenticators,
+            mark some of them as <literal>required</literal>, configure some authenticators etc). Or you can even create new authentication flow and/or
+            write your own Authenticator implementations and use it in your flow. See <link linkend="auth_spi">Authentication Flows SPI</link> for more details on how to do it.
+        </para>
+        <para>
+            For <literal>First Broker Login</literal> case, it might be useful if your Authenticator is subclass of <literal>org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator</literal>
+            so you have access to all details about authenticated Identity provider account. But it's not a requirement.
+        </para>
+        <section>
+            <title>Default First Login Flow</title>
+            <para>
+                Let's describe the default behaviour provided by <literal>First Broker Login</literal> flow. There are those authenticators:
+                <variablelist>
+                    <varlistentry>
+                        <term>Review Profile</term>
+                        <listitem>
+                            <para>
+                                This authenticator might display the profile info page, where user can review his profile retrieved from identity provider.
+                                The authenticator is configurable. You can set <literal>Update Profile On First Login</literal> option.
+                                When <literal>On</literal>, users will be always presented with the profile page asking for additional information
+                                in order to federate their identities. When <literal>missing</literal>, users will be presented with
+                                the profile page only if some mandatory information (email, first name, last name) is not provided by identity provider.
+                                If <literal>Off</literal>, the profile page won't be displayed, unless user clicks in later phase on <literal>Review profile info</literal>
+                                link (page displayed in later phase by <literal>Confirm Link Existing Account</literal> authenticator)
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <varlistentry>
+                        <term>Create User If Unique</term>
+                        <listitem>
+                            <para>
+                                This authenticator checks if there is already existing Keycloak account with same email or username like
+                                the account from identity provider. If it's not, then authenticator just creates new Keyclok account and
+                                link it with identity provider and whole flow is finished. Otherwise it goes to the next <literal>Handle Existing Account</literal> subflow.
+                                If you always want to ensure that there is no duplicated account, you can mark this authenticator as <literal>REQUIRED</literal> .
+                                In this case, the user will see the error page if there is existing Keycloak account and user needs
+                                to link his identity provider account through Account management.
+                            </para>
+                            <para>
+                                This authenticator also has config option <literal>Require Password Update After Registration</literal> .
+                                When enabled, user is required to update password after account is created.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <varlistentry>
+                        <term>Confirm Link Existing Account</term>
+                        <listitem>
+                            <para>
+                                User will see the info page, that there is existing Keycloak account with same email. He can either
+                                review his profile again and use different email or username (flow is restarted and goes back to <literal>Review Profile</literal> authenticator).
+                                Or he can confirm that he wants to link identity provider account with his existing Keycloak account.
+                                Disable this authenticator if you don't want users to see this confirmation page, but go straight
+                                to linking identity provider account by email verification or re-authentication.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <varlistentry>
+                        <term>Verify Existing Account By Email</term>
+                        <listitem>
+                            <para>
+                                This authenticator is <literal>ALTERNATIVE</literal> by default, so it's used only if realm has SMTP setup configured.
+                                It will send mail to user, where he can confirm that he wants to link identity provider with his Keycloak account.
+                                Disable this if you don't want to confirm linking by email, but instead you always want users to reauthenticate with their password (and alternatively OTP).
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                    <varlistentry>
+                        <term>Verify Existing Account By Re-authentication</term>
+                        <listitem>
+                            <para>
+                                This authenticator is used if email authenticator is disabled or non-available (SMTP not configured for realm). It
+                                will display login screen where user needs to authenticate with his password to link his Keycloak account with Identity provider.
+                                User can also re-authenticate with some different identity provider, which is already linked to his keycloak account.
+                                You can also force users to use OTP, otherwise it's optional and used only if OTP is already set for user account.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+
+                </variablelist>
+            </para>
+        </section>
+    </section>
+
     <section>
         <title>Examples</title>
         <para>
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml b/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml
index 650ed76..b0a443d 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml
@@ -80,15 +80,33 @@
     <section>
         <title>Version specific migration</title>
         <section>
+            <title>Migrating to 1.7.0.CR1</title>
+            <simplesect>
+                <title>Option 'Update Profile On First Login' moved from Identity provider to Review Profile authenticator</title>
+                <para>
+                    In this version, we added <literal>First Broker Login</literal>, which allows you to specify what exactly should be done
+                    when new user is logged through Identity provider (or Social provider), but there is no existing Keycloak user
+                    yet linked to the social account. As part of this work, we added option <literal>First Login Flow</literal> to identity providers where
+                    you can specify the flow and then you can configure this flow under <literal>Authentication</literal> tab in admin console.
+                </para>
+                <para>
+                    We also removed the option <literal>Update Profile On First Login</literal> from the Identity provider settings and moved it
+                    to the configuration of <literal>Review Profile</literal> authenticator. So once you specify which flow should be used for your
+                    Identity provider (by default it's <literal>First Broker Login</literal> flow), you go to <literal>Authentication</literal> tab, select the flow
+                    and then you configure the option under <literal>Review Profile</literal> authenticator.
+                </para>
+            </simplesect>
+        </section>
+        <section>
             <title>Migrating to 1.6.0.Final</title>
             <simplesect>
-                <title>Refresh tokens are not reusable anymore</title>
+                <title>Option that refresh tokens are not reusable anymore</title>
                 <para>
-                    Old versions of Keycloak allowed reusing refresh tokens multiple times. Keycloak no longer permits
-                    this by default. When a refresh token is used to obtain a new access token a new refresh token is also
-                    included. This new refresh token should be used next time the access token is refreshed. If this is
-                    a problem for you it's possible to enable reuse of refresh tokens in the admin console under token
-                    settings.
+                    Old versions of Keycloak allowed reusing refresh tokens multiple times. Keycloak still permits this,
+                    but also have an option <literal>Revoke refresh token</literal> to disallow it. Option is in in admin console under token settings.
+                    When a refresh token is used to obtain a new access token a new refresh token is also
+                    included. When option is enabled, then this new refresh token should be used next time the access token is refreshed.
+                    It won't be possible to reuse old refresh token multiple times.
                 </para>
             </simplesect>
             <simplesect>
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/providers.xml b/docbook/auth-server-docs/reference/en/en-US/modules/providers.xml
index eda5aca..71c2919 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/providers.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/providers.xml
@@ -16,7 +16,7 @@
         </para>
         <para>
             For example to implement the Event Listener SPI you start by implementing EventListenerProviderFactory:
-<programlisting><![CDATA[{
+<programlisting><![CDATA[
 package org.acme.provider;
 
 import ...
@@ -43,7 +43,7 @@ public class MyEventListenerProviderFactory implements EventListenerProviderFact
     }
 
 }
-}]]></programlisting>
+]]></programlisting>
             The example uses an imagined MaxList which has a maximum size and is concurrency safe. When the maximum size is reached
             and new entries are added the oldest entry is removed. Keycloak creates a single instance of
             EventListenerProviderFactory which makes it possible to store state for multiple requests. EventListenerProvider
@@ -51,7 +51,7 @@ public class MyEventListenerProviderFactory implements EventListenerProviderFact
         </para>
         <para>
             Next you would implement EventListenerProvider:
-<programlisting><![CDATA[{
+<programlisting><![CDATA[
 package org.acme.provider;
 
 import ...
@@ -75,13 +75,61 @@ public class MyEventListenerProvider implements EventListenerProvider {
     }
 
 }
-}]]></programlisting>
+]]></programlisting>
         </para>
         <para>
             The file <literal>META-INF/services/org.keycloak.events.EventListenerProviderFactory</literal> should
             contain the full name of your ProviderFactory implementation:
 <programlisting><![CDATA[org.acme.provider.MyEventListenerProviderFactory]]></programlisting>
         </para>
+        
+        <section>
+            <title>Show info from you SPI implementation in Keycloak admin console</title>
+            <para>
+                Sometimes it is useful to show additional info about your Provider to a Keycloak administrator.
+                You can show provider build time informations (eg. version of custom provider currently installed), 
+                current configuration of the provider (eg. url of remote system your provider talks to) or some operational 
+                info (average time of response from remote system your provider talks to). 
+                Keycloak admin console provides Server Info page to show this kind of information.
+             </para>
+             <para>   
+                To show info from your provider it is enough to implement 
+                <literal>org.keycloak.provider.ServerInfoAwareProviderFactory</literal> interface in your ProviderFactory. 
+                Example implementation for MyEventListenerProviderFactory from previous example:
+                <programlisting><![CDATA[
+package org.acme.provider;
+
+import ...
+
+public class MyEventListenerProviderFactory implements EventListenerProviderFactory, ServerInfoAwareProviderFactory {
+
+    private List<Event> events;
+    private int max;
+     
+...
+
+    @Override
+    public void init(Config.Scope config) {
+        max = config.getInt("max");
+        events = new MaxList(max);
+    }
+
+...
+
+    @Override
+    public Map<String, String> getOperationalInfo() {
+        Map<String, String> ret = new LinkedHashMap<>();
+        ret.put("version", "1.0");
+        ret.put("listSizeMax", max + "");
+        ret.put("listSizeCurrent", events.size() + "");
+        return ret;
+    }
+
+}
+]]></programlisting>
+            
+            </para>
+        </section>    
     </section>
 
     <section>
@@ -98,13 +146,13 @@ public class MyEventListenerProvider implements EventListenerProvider {
                 To register a provider using Modules first create a module. To do this you can either use the jboss-cli
                 script or manually create a folder inside KEYCLOAK_HOME/modules and add your jar and a <literal>module.xml</literal>.
                 For example to add the event listener sysout example provider using the jboss-cli script execute:
-<programlisting><![CDATA[{
+<programlisting><![CDATA[
     KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.event-sysout --resources=target/event-listener-sysout-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-model-api,org.keycloak.keycloak-events-api"
-}]]></programlisting>
+]]></programlisting>
                 Or to manually create it start by creating the folder <literal>KEYCLOAK_HOME/modules/org/keycloak/examples/event-sysout/main</literal>.
                 Then copy <literal>event-listener-sysout-example.jar</literal> to this folder and create <literal>module.xml</literal>
                 with the following content:
-<programlisting><![CDATA[{
+<programlisting><![CDATA[
 <?xml version="1.0" encoding="UTF-8"?>
 <module xmlns="urn:jboss:module:1.1" name="org.keycloak.examples.event-sysout">
     <resources>
@@ -116,7 +164,7 @@ public class MyEventListenerProvider implements EventListenerProvider {
         <module name="org.keycloak.keycloak-events-api"/>
     </dependencies>
 </module>
-}]]></programlisting>
+]]></programlisting>
             </para>
             <para>
                 Once you've created the module you need to register this module with Keycloak. This is done by editing
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/roles.xml b/docbook/auth-server-docs/reference/en/en-US/modules/roles.xml
index 9cd6176..b7c034c 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/roles.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/roles.xml
@@ -1,7 +1,7 @@
 <chapter id="roles">
     <title>Roles</title>
     <para>
-        In Keycloak, roles (or permissions) can be defined globally at the realm level, or individually per application.
+        In Keycloak, roles can be defined globally at the realm level, or individually per application.
         Each role has a name which must be unique at the level it is defined in, i.e. you can have only one "admin" role at
         the realm level.  You may have that a role named "admin" within an Application too, but "admin" must be unique
         for that application.
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml b/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml
index 558f943..78d9a4b 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml
@@ -43,9 +43,9 @@
 
 
         <section id="overlay_install">
-            <title>Install on existing WildFly 9.0.1.Final</title>
+            <title>Install on existing WildFly &wildfly.version;</title>
             <para>
-                Keycloak can be installed into an existing WildFly 9.0.0.Final server. To do this download
+                Keycloak can be installed into an existing WildFly &wildfly.version; server. To do this download
                 <literal>keycloak-overlay-&project.version;.zip</literal> or <literal>keycloak-overlay-&project.version;.tar.gz</literal>.
                 Once downloaded extract into the root directory of your WildFly installation. To start WildFly with Keycloak
                 run:
@@ -62,11 +62,15 @@
             <para>
                 To add Keycloak to other sever configurations (standalone.xml, standalone-ha.xml, etc.) start the server with
                 the desired server-config. If you are running the server in standalone mode run:
-                <programlisting>cd &lt;WILDFLY_HOME&gt;/bin
-                    ./jboss-cli.sh -c --file=keycloak-install.cli</programlisting>
+<programlisting>
+cd &lt;WILDFLY_HOME&gt;/bin
+./jboss-cli.sh -c --file=keycloak-install.cli
+</programlisting>
                 Or if you are running in clustering (HA) mode (by having used -c standalone-ha.xml) then run:
-                <programlisting>cd &lt;WILDFLY_HOME&gt;/bin
-                    ./jboss-cli.sh -c --file=keycloak-install-ha.cli</programlisting>
+<programlisting>
+cd &lt;WILDFLY_HOME&gt;/bin
+./jboss-cli.sh -c --file=keycloak-install-ha.cli
+</programlisting>
                 You may see exceptions in the server log, but after restarting the server they should be gone.
                 You can restart the server with:
                 <programlisting>&lt;WILDFLY_HOME&gt;/bin/jboss-cli.sh -c :reload</programlisting>
@@ -75,7 +79,7 @@
         <section>
             <title>Install on existing JBoss EAP 6.4.0.GA</title>
             <para>
-                Same procedure as WildFly 9.0.1.Final, but download <literal>keycloak-overlay-eap6-&project.version;.zip</literal> or <literal>keycloak-overlay-eap6-&project.version;.tar.gz</literal>.
+                Same procedure as WildFly &wildfly.version;, but download <literal>keycloak-overlay-eap6-&project.version;.zip</literal> or <literal>keycloak-overlay-eap6-&project.version;.tar.gz</literal>.
             </para>
         </section>
         <section>
@@ -85,7 +89,7 @@
                 To install it first download <literal>keycloak-demo-&project.version;.zip</literal> or
                 <literal>keycloak-demo-&project.version;.tar.gz</literal>. Once downloaded extract it inside
                 <literal>keycloak-demo-&project.version;</literal> you'll find <literal>keycloak</literal> which contains
-                a full WildFly 9.0.0.Final server with Keycloak Server and Adapters included. You'll also find <literal>docs</literal>
+                a full WildFly &wildfly.version; server with Keycloak Server and Adapters included. You'll also find <literal>docs</literal>
                 and <literal>examples</literal> which contains everything you need to get started developing applications that use Keycloak.
             </para>
             <para>
@@ -437,12 +441,12 @@ All configuration options are optional. Default value for directory is <literal>
                 settings you can specify before boot time.  This is configured in the
                 <literal>standalone/configuration/keycloak-server.json</literal>.
                 By default the setting is like this:
-                <programlisting><![CDATA[
-    "connectionsHttpClient": {
-        "default": {
-            "disable-trust-manager": true
-        }
-    },
+<programlisting><![CDATA[
+"connectionsHttpClient": {
+    "default": {
+        "disable-trust-manager": true
+    }
+},
 ]]></programlisting>
                 Possible configuration options are:
                 <variablelist>
@@ -659,25 +663,25 @@ All configuration options are optional. Default value for directory is <literal>
                             to do with the <literal>keytool</literal> utility that comes with the Java jdk.
                         </para>
                         <para>
-    <programlisting>
-    $ keytool -genkey -alias localhost -keyalg RSA -keystore keycloak.jks -validity 10950
-        Enter keystore password: secret
-        Re-enter new password: secret
-        What is your first and last name?
-        [Unknown]:  localhost
-        What is the name of your organizational unit?
-        [Unknown]:  Keycloak
-        What is the name of your organization?
-        [Unknown]:  Red Hat
-        What is the name of your City or Locality?
-        [Unknown]:  Westford
-        What is the name of your State or Province?
-        [Unknown]:  MA
-        What is the two-letter country code for this unit?
-        [Unknown]:  US
-        Is CN=localhost, OU=Keycloak, O=Test, L=Westford, ST=MA, C=US correct?
-        [no]:  yes
-    </programlisting>
+<programlisting>
+$ keytool -genkey -alias localhost -keyalg RSA -keystore keycloak.jks -validity 10950
+    Enter keystore password: secret
+    Re-enter new password: secret
+    What is your first and last name?
+    [Unknown]:  localhost
+    What is the name of your organizational unit?
+    [Unknown]:  Keycloak
+    What is the name of your organization?
+    [Unknown]:  Red Hat
+    What is the name of your City or Locality?
+    [Unknown]:  Westford
+    What is the name of your State or Province?
+    [Unknown]:  MA
+    What is the two-letter country code for this unit?
+    [Unknown]:  US
+    Is CN=localhost, OU=Keycloak, O=Test, L=Westford, ST=MA, C=US correct?
+    [no]:  yes
+</programlisting>
                         </para>
                         <para>
                             You should answer <literal>What is your first and last name ?</literal> question with
@@ -693,44 +697,44 @@ All configuration options are optional. Default value for directory is <literal>
                         </para>
                         <para>
                             The first thing to do is generate a Certificate Request:
-    <programlisting>
-    $ keytool -certreq -alias yourdomain -keystore keycloak.jks > keycloak.careq
-    </programlisting>
+<programlisting>
+$ keytool -certreq -alias yourdomain -keystore keycloak.jks > keycloak.careq
+</programlisting>
                         </para>
                         <para>
                              Where <literal>yourdomain</literal> is a DNS name for which this certificate is generated for.
                              Keytool generates the request:
-    <programlisting>
-    -----BEGIN NEW CERTIFICATE REQUEST-----
-    MIIC2jCCAcICAQAwZTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAk1BMREwDwYDVQQHEwhXZXN0Zm9y
-    ZDEQMA4GA1UEChMHUmVkIEhhdDEQMA4GA1UECxMHUmVkIEhhdDESMBAGA1UEAxMJbG9jYWxob3N0
-    MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr7kck2TaavlEOGbcpi9c0rncY4HhdzmY
-    Ax2nZfq1eZEaIPqI5aTxwQZzzLDK9qbeAd8Ji79HzSqnRDxNYaZu7mAYhFKHgixsolE3o5Yfzbw1
-    29Rvy+eUVe+WZxv5oo9wolVVpdSINIMEL2LaFhtX/c1dqiqYVpfnvFshZQaIg2nL8juzZcBjj4as
-    H98gIS7khql/dkZKsw9NLvyxgJvp7PaXurX29fNf3ihG+oFrL22oFyV54BWWxXCKU/GPn61EGZGw
-    Ft2qSIGLdctpMD1aJR2bcnlhEjZKDksjQZoQ5YMXaAGkcYkG6QkgrocDE2YXDbi7GIdf9MegVJ35
-    2DQMpwIDAQABoDAwLgYJKoZIhvcNAQkOMSEwHzAdBgNVHQ4EFgQUQwlZJBA+fjiDdiVzaO9vrE/i
-    n2swDQYJKoZIhvcNAQELBQADggEBAC5FRvMkhal3q86tHPBYWBuTtmcSjs4qUm6V6f63frhveWHf
-    PzRrI1xH272XUIeBk0gtzWo0nNZnf0mMCtUBbHhhDcG82xolikfqibZijoQZCiGiedVjHJFtniDQ
-    9bMDUOXEMQ7gHZg5q6mJfNG9MbMpQaUVEEFvfGEQQxbiFK7hRWU8S23/d80e8nExgQxdJWJ6vd0X
-    MzzFK6j4Dj55bJVuM7GFmfdNC52pNOD5vYe47Aqh8oajHX9XTycVtPXl45rrWAH33ftbrS8SrZ2S
-    vqIFQeuLL3BaHwpl3t7j2lMWcK1p80laAxEASib/fAwrRHpLHBXRcq6uALUOZl4Alt8=
-    -----END NEW CERTIFICATE REQUEST-----
-     </programlisting>
+<programlisting>
+-----BEGIN NEW CERTIFICATE REQUEST-----
+MIIC2jCCAcICAQAwZTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAk1BMREwDwYDVQQHEwhXZXN0Zm9y
+ZDEQMA4GA1UEChMHUmVkIEhhdDEQMA4GA1UECxMHUmVkIEhhdDESMBAGA1UEAxMJbG9jYWxob3N0
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr7kck2TaavlEOGbcpi9c0rncY4HhdzmY
+Ax2nZfq1eZEaIPqI5aTxwQZzzLDK9qbeAd8Ji79HzSqnRDxNYaZu7mAYhFKHgixsolE3o5Yfzbw1
+29Rvy+eUVe+WZxv5oo9wolVVpdSINIMEL2LaFhtX/c1dqiqYVpfnvFshZQaIg2nL8juzZcBjj4as
+H98gIS7khql/dkZKsw9NLvyxgJvp7PaXurX29fNf3ihG+oFrL22oFyV54BWWxXCKU/GPn61EGZGw
+Ft2qSIGLdctpMD1aJR2bcnlhEjZKDksjQZoQ5YMXaAGkcYkG6QkgrocDE2YXDbi7GIdf9MegVJ35
+2DQMpwIDAQABoDAwLgYJKoZIhvcNAQkOMSEwHzAdBgNVHQ4EFgQUQwlZJBA+fjiDdiVzaO9vrE/i
+n2swDQYJKoZIhvcNAQELBQADggEBAC5FRvMkhal3q86tHPBYWBuTtmcSjs4qUm6V6f63frhveWHf
+PzRrI1xH272XUIeBk0gtzWo0nNZnf0mMCtUBbHhhDcG82xolikfqibZijoQZCiGiedVjHJFtniDQ
+9bMDUOXEMQ7gHZg5q6mJfNG9MbMpQaUVEEFvfGEQQxbiFK7hRWU8S23/d80e8nExgQxdJWJ6vd0X
+MzzFK6j4Dj55bJVuM7GFmfdNC52pNOD5vYe47Aqh8oajHX9XTycVtPXl45rrWAH33ftbrS8SrZ2S
+vqIFQeuLL3BaHwpl3t7j2lMWcK1p80laAxEASib/fAwrRHpLHBXRcq6uALUOZl4Alt8=
+-----END NEW CERTIFICATE REQUEST-----
+</programlisting>
                         </para>
                         <para>
                             Send this ca request to your CA.  The CA will issue you a signed certificate and send it to you.
                             Before you import your new cert, you must obtain and import the root certificate of the CA.
                             You can download the cert from CA (ie.: root.crt) and import as follows:
-    <programlisting>
-    $ keytool -import -keystore keycloak.jks -file root.crt -alias root
-    </programlisting>
+<programlisting>
+$ keytool -import -keystore keycloak.jks -file root.crt -alias root
+</programlisting>
                         </para>
                         <para>
                             Last step is import your new CA generated certificate to your keystore:
-    <programlisting>
-    $ keytool -import -alias yourdomain -keystore keycloak.jks -file your-certificate.cer
-    </programlisting>
+<programlisting>
+$ keytool -import -alias yourdomain -keystore keycloak.jks -file your-certificate.cer
+</programlisting>
                         </para>
                     </section>
                 </section>
@@ -744,18 +748,19 @@ All configuration options are optional. Default value for directory is <literal>
                     </para>
                     <para>
                         To the <literal>security-realms</literal> element add:
-                        <programlisting><![CDATA[<security-realm name="UndertowRealm">
-        <server-identities>
-            <ssl>
-                <keystore path="keycloak.jks" relative-to="jboss.server.config.dir" keystore-password="secret" />
-            </ssl>
-        </server-identities>
-    </security-realm>]]></programlisting>
+<programlisting><![CDATA[
+<security-realm name="UndertowRealm">
+    <server-identities>
+        <ssl>
+            <keystore path="keycloak.jks" relative-to="jboss.server.config.dir" keystore-password="secret" />
+        </ssl>
+    </server-identities>
+</security-realm>
+]]></programlisting>
                     </para>
                     <para>
                         Find the element <literal>&lt;server name="default-server"&gt;</literal> (it's a child element of <literal>&lt;subsystem xmlns="urn:jboss:domain:undertow:1.0"&gt;</literal>) and add:
-                        <programlisting><![CDATA[<https-listener name="https" socket-binding="https" security-realm="UndertowRealm"/>
-    ]]></programlisting>
+                        <programlisting><![CDATA[<https-listener name="https" socket-binding="https" security-realm="UndertowRealm"/>]]></programlisting>
                     </para>
                     <para>
                         Check the <ulink url="https://docs.jboss.org/author/display/WFLY8/Undertow+(web)+subsystem+configuration">Wildfly Undertow</ulink> documentation for more information on fine tuning the socket connections.
@@ -813,45 +818,21 @@ All configuration options are optional. Default value for directory is <literal>
     </section>
 
     <section>
-        <title>Adding Keycloak server in Domain Mode</title>
+        <title>Keycloak server in Domain Mode</title>
         <para>
             In domain mode, you start the server with the "domain" command instead of the "standalone" command.  In this case, the Keycloak subsystem is
             defined in domain/configuration/domain.xml instead of standalone/configuration.standalone.xml.  Inside domain.xml, you will see more than one
-            profile.  A Keycloak subsystem can be defined in zero or more of those profiles.
+            profile.  The Keycloak subsystem is defined for all initial profiles.
         </para>
         <para>
-            To enable Keycloak for a server profile edit domain/configuration/domain.xml. To the <literal>extensions</literal>
-            element add the Keycloak extension:
-<programlisting><![CDATA[
-<extensions>
-    ...
-    <extension module="org.keycloak.keycloak-subsystem"/>
-</extensions>
-]]></programlisting>
-            Then you need to add the server to the required server profiles. By default WildFly starts two servers
-            in the main-server-group which uses the full profile. To add Keycloak for this profile add the Keycloak
-            subsystem to the <literal>profile</literal> element with <literal>name</literal> full:
-<programlisting><![CDATA[
-<profile name="full">
-    ...
-    <subsystem xmlns="urn:jboss:domain:keycloak:1.0">
-        <auth-server name="main-auth-server">
-            <enabled>true</enabled>
-            <web-context>auth</web-context>
-        </auth-server>
-    </subsystem>
-]]></programlisting>
+            THe server is also added to server profiles. By default two servers are started
+            in the main-server-group which uses the full profile.
         </para>
         <para>
-            To configure the server copy <literal>standalone/configuration/keycloak-server.json</literal> to
-            <literal>domain/servers/&lt;SERVER NAME&gt;/configuration</literal>. The configuration should be identical
+            You need to make sure <literal>domain/servers/&lt;SERVER NAME&gt;/configuration</literal> is identical
             for all servers in a group.
         </para>
         <para>
-            Follow the <link linkend='clustering'>Clustering</link> section of the documentation to configure Keycloak
-            for clustering. In domain mode it doesn't make much sense to not configure Keycloak in cluster mode.
-        </para>
-        <para>
             To deploy custom providers and themes you should deploys these as modules and make sure the modules are
             available to all servers in the group. See <link linkend='providers'>Providers</link> and
             <link linkend='themes'>Themes</link> sections for more information on how to do this.
@@ -865,12 +846,12 @@ All configuration options are optional. Default value for directory is <literal>
         </para>
         <para>
             To do this, add the <literal>default-web-module</literal> attribute in the Undertow subystem in standalone.xml.
-            <programlisting><![CDATA[
+<programlisting><![CDATA[
 <subsystem xmlns="urn:jboss:domain:undertow:2.0">
-            <server name="default-server">
-                <host name="default-host" alias="localhost" default-web-module="keycloak-server.war">
-                    <location name="/" handler="welcome-content"/>
-                </host>
+    <server name="default-server">
+        <host name="default-host" alias="localhost" default-web-module="keycloak-server.war">
+            <location name="/" handler="welcome-content"/>
+        </host>
 ]]></programlisting>
         </para>
         <para>
diff --git a/events/api/src/main/java/org/keycloak/events/Errors.java b/events/api/src/main/java/org/keycloak/events/Errors.java
index d7ca253..b0cbc6a 100755
--- a/events/api/src/main/java/org/keycloak/events/Errors.java
+++ b/events/api/src/main/java/org/keycloak/events/Errors.java
@@ -44,8 +44,7 @@ public interface Errors {
 
     String NOT_ALLOWED = "not_allowed";
 
-    String FEDERATED_IDENTITY_EMAIL_EXISTS = "federated_identity_email_exists";
-    String FEDERATED_IDENTITY_USERNAME_EXISTS = "federated_identity_username_exists";
+    String FEDERATED_IDENTITY_EXISTS = "federated_identity_account_exists";
     String SSL_REQUIRED = "ssl_required";
 
     String USER_SESSION_NOT_FOUND = "user_session_not_found";
@@ -53,4 +52,5 @@ public interface Errors {
     String EMAIL_SEND_FAILED = "email_send_failed";
     String INVALID_EMAIL = "invalid_email";
     String IDENTITY_PROVIDER_LOGIN_FAILURE = "identity_provider_login_failure";
+    String IDENTITY_PROVIDER_ERROR = "identity_provider_error";
 }
diff --git a/events/api/src/main/java/org/keycloak/events/EventType.java b/events/api/src/main/java/org/keycloak/events/EventType.java
index 0a8b3ae..b75728b 100755
--- a/events/api/src/main/java/org/keycloak/events/EventType.java
+++ b/events/api/src/main/java/org/keycloak/events/EventType.java
@@ -48,6 +48,8 @@ public enum EventType {
     SEND_VERIFY_EMAIL_ERROR(true),
     SEND_RESET_PASSWORD(true),
     SEND_RESET_PASSWORD_ERROR(true),
+    SEND_IDENTITY_PROVIDER_LINK(true),
+    SEND_IDENTITY_PROVIDER_LINK_ERROR(true),
     RESET_PASSWORD(true),
     RESET_PASSWORD_ERROR(true),
 
@@ -60,12 +62,12 @@ public enum EventType {
 
     IDENTITY_PROVIDER_LOGIN(false),
     IDENTITY_PROVIDER_LOGIN_ERROR(false),
+    IDENTITY_PROVIDER_FIRST_LOGIN(true),
+    IDENTITY_PROVIDER_FIRST_LOGIN_ERROR(true),
     IDENTITY_PROVIDER_RESPONSE(false),
     IDENTITY_PROVIDER_RESPONSE_ERROR(false),
     IDENTITY_PROVIDER_RETRIEVE_TOKEN(false),
     IDENTITY_PROVIDER_RETRIEVE_TOKEN_ERROR(false),
-    IDENTITY_PROVIDER_ACCCOUNT_LINKING(false),
-    IDENTITY_PROVIDER_ACCCOUNT_LINKING_ERROR(false),
     IMPERSONATE(true),
     CUSTOM_REQUIRED_ACTION(true),
     CUSTOM_REQUIRED_ACTION_ERROR(true),
diff --git a/examples/js-console/src/main/webapp/WEB-INF/web.xml b/examples/js-console/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..a7460c2
--- /dev/null
+++ b/examples/js-console/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app xmlns="http://java.sun.com/xml/ns/javaee"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+         version="3.0">
+
+    <module-name>js-console</module-name>
+</web-app>
\ No newline at end of file
diff --git a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationProvider.java b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationProvider.java
index f622895..d6475aa 100755
--- a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationProvider.java
+++ b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationProvider.java
@@ -1,6 +1,7 @@
 package org.keycloak.examples.federation.properties;
 
 import org.keycloak.models.CredentialValidationOutput;
+import org.keycloak.models.GroupModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
@@ -106,6 +107,12 @@ public abstract class BasePropertiesFederationProvider implements UserFederation
 
     }
 
+    @Override
+    public void preRemove(RealmModel realm, GroupModel group) {
+        // complete we dont'care if a role is removed
+
+    }
+
     /**
      * See if the user is still in the properties file
      *
diff --git a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationProvider.java b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationProvider.java
index 164cf79..5467a42 100755
--- a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationProvider.java
+++ b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationProvider.java
@@ -63,4 +63,6 @@ public class ClasspathPropertiesFederationProvider extends BasePropertiesFederat
         throw new IllegalStateException("Remove not supported");
     }
 
+
+
 }
diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
index 30eb055..12b358e 100755
--- a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
+++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
@@ -7,6 +7,7 @@ import org.codehaus.jackson.JsonGenerator;
 import org.codehaus.jackson.map.ObjectMapper;
 import org.codehaus.jackson.map.SerializationConfig;
 import org.keycloak.models.ClientModel;
+import org.keycloak.models.GroupModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleContainerModel;
@@ -15,6 +16,7 @@ import org.keycloak.models.FederatedIdentityModel;
 import org.keycloak.models.UserConsentModel;
 import org.keycloak.models.UserCredentialValueModel;
 import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.CredentialRepresentation;
@@ -294,6 +296,12 @@ public class ExportUtils {
             }
         }
 
+        List<String> groups = new LinkedList<>();
+        for (GroupModel group : user.getGroups()) {
+            groups.add(ModelToRepresentation.buildGroupPath(group));
+        }
+        userRep.setGroups(groups);
+
         return userRep;
     }
 
diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosUsernamePasswordAuthenticator.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosUsernamePasswordAuthenticator.java
index ea3f953..f38eeb5 100644
--- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosUsernamePasswordAuthenticator.java
+++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosUsernamePasswordAuthenticator.java
@@ -17,6 +17,7 @@ import javax.security.auth.login.LoginException;
 
 import org.jboss.logging.Logger;
 import org.keycloak.federation.kerberos.CommonKerberosConfig;
+import org.keycloak.models.ModelException;
 
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -54,6 +55,8 @@ public class KerberosUsernamePasswordAuthenticator {
             String message = le.getMessage();
             logger.debug("Message from kerberos: " + message);
 
+            checkKerberosServerAvailable(le);
+
             // Bit cumbersome, but seems to work with tested kerberos servers
             boolean exists = (!message.contains("Client not found"));
             return exists;
@@ -74,11 +77,19 @@ public class KerberosUsernamePasswordAuthenticator {
             logoutSubject();
             return true;
         } catch (LoginException le) {
+            checkKerberosServerAvailable(le);
+
             logger.debug("Failed to authenticate user " + username, le);
             return false;
         }
     }
 
+    protected void checkKerberosServerAvailable(LoginException le) {
+        if (le.getMessage().contains("Port Unreachable")) {
+            throw new ModelException("Kerberos unreachable", le);
+        }
+    }
+
 
     /**
      * Returns true if user was successfully authenticated against Kerberos
diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java
old mode 100644
new mode 100755
index ec1a905..65831f2
--- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java
+++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java
@@ -12,6 +12,7 @@ import org.jboss.logging.Logger;
 import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator;
 import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
 import org.keycloak.models.CredentialValidationOutput;
+import org.keycloak.models.GroupModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
@@ -106,11 +107,16 @@ public class KerberosFederationProvider implements UserFederationProvider {
     }
 
     @Override
+    public void preRemove(RealmModel realm, GroupModel group) {
+
+    }
+
+    @Override
     public boolean isValid(RealmModel realm, UserModel local) {
         // KerberosUsernamePasswordAuthenticator.isUserAvailable is an overhead, so avoid it for now
 
         String kerberosPrincipal = local.getUsername() + "@" + kerberosConfig.getKerberosRealm();
-        return kerberosPrincipal.equals(local.getFirstAttribute(KERBEROS_PRINCIPAL));
+        return kerberosPrincipal.equalsIgnoreCase(local.getFirstAttribute(KERBEROS_PRINCIPAL));
     }
 
     @Override
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
index 322155b..3704f76 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
@@ -12,6 +12,7 @@ import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
 import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig;
 import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
 import org.keycloak.models.CredentialValidationOutput;
+import org.keycloak.models.GroupModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.LDAPConstants;
 import org.keycloak.models.ModelDuplicateException;
@@ -319,6 +320,11 @@ public class LDAPFederationProvider implements UserFederationProvider {
         // TODO: Maybe mappers callback to ensure role deletion propagated to LDAP by RoleLDAPFederationMapper?
     }
 
+    @Override
+    public void preRemove(RealmModel realm, GroupModel group) {
+
+    }
+
     public boolean validPassword(RealmModel realm, UserModel user, String password) {
         if (kerberosConfig.isAllowKerberosAuthentication() && kerberosConfig.isUseKerberosForPasswordAuthentication()) {
             // Use Kerberos JAAS (Krb5LoginModule)
diff --git a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ExtendingThemeManager.java b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ExtendingThemeManager.java
index 020d349..0a253d1 100644
--- a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ExtendingThemeManager.java
+++ b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ExtendingThemeManager.java
@@ -153,6 +153,10 @@ public class ExtendingThemeManager implements ThemeProvider {
 
         private List<Theme> themes;
 
+        private Properties properties;
+
+        private ConcurrentHashMap<String, ConcurrentHashMap<Locale, Properties>> messages = new ConcurrentHashMap<>();
+
         public ExtendingTheme(List<Theme> themes) {
             this.themes = themes;
         }
@@ -229,28 +233,41 @@ public class ExtendingThemeManager implements ThemeProvider {
 
         @Override
         public Properties getMessages(String baseBundlename, Locale locale) throws IOException {
-            Properties messages = new Properties();
-            ListIterator<Theme> itr = themes.listIterator(themes.size());
-            while (itr.hasPrevious()) {
-                Properties m = itr.previous().getMessages(baseBundlename, locale);
-                if (m != null) {
-                    messages.putAll(m);
+            if (messages.get(baseBundlename) == null || messages.get(baseBundlename).get(locale) == null) {
+                Properties messages = new Properties();
+                ListIterator<Theme> itr = themes.listIterator(themes.size());
+                while (itr.hasPrevious()) {
+                    Properties m = itr.previous().getMessages(baseBundlename, locale);
+                    if (m != null) {
+                        messages.putAll(m);
+                    }
                 }
+
+                this.messages.putIfAbsent(baseBundlename, new ConcurrentHashMap<Locale, Properties>());
+                this.messages.get(baseBundlename).putIfAbsent(locale, messages);
+
+                return messages;
+            } else {
+                return messages.get(baseBundlename).get(locale);
             }
-            return messages;
         }
 
         @Override
         public Properties getProperties() throws IOException {
-            Properties properties = new Properties();
-            ListIterator<Theme> itr = themes.listIterator(themes.size());
-            while (itr.hasPrevious()) {
-                Properties p = itr.previous().getProperties();
-                if (p != null) {
-                    properties.putAll(p);
+            if (properties == null) {
+                Properties properties = new Properties();
+                ListIterator<Theme> itr = themes.listIterator(themes.size());
+                while (itr.hasPrevious()) {
+                    Properties p = itr.previous().getProperties();
+                    if (p != null) {
+                        properties.putAll(p);
+                    }
                 }
+                this.properties = properties;
+                return properties;
+            } else {
+                return properties;
             }
-            return properties;
         }
 
     }
diff --git a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_de.properties b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_de.properties
index 97f2d66..33d080d 100644
--- a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_de.properties
+++ b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_de.properties
@@ -126,4 +126,4 @@ locale_de=Deutsch
 locale_en=English
 locale_it=Italian
 locale_pt-BR=Portugu\u00EAs (Brasil)
-locale_fr=Fran�ais
\ No newline at end of file
+locale_fr=Fran\u00e7ais
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties
index f85ebff..4980104 100755
--- a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties
+++ b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties
@@ -133,6 +133,7 @@ federatedIdentityLinkNotActiveMessage=This identity is not active anymore.
 federatedIdentityRemovingLastProviderMessage=You can''t remove last federated identity as you don''t have password.
 identityProviderRedirectErrorMessage=Failed to redirect to identity provider.
 identityProviderRemovedMessage=Identity provider removed successfully.
+identityProviderAlreadyLinkedMessage=Federated identity returned by {0} is already linked to another user.
 
 accountDisabledMessage=Account is disabled, contact admin.
 
@@ -150,4 +151,4 @@ locale_de=German
 locale_en=English
 locale_it=Italian
 locale_pt-BR=Portugu\u00EAs (Brasil)
-locale_fr=Fran�ais
\ No newline at end of file
+locale_fr=Fran\u00e7ais
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_es.properties b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_es.properties
new file mode 100644
index 0000000..40743f8
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_es.properties
@@ -0,0 +1,154 @@
+doSave=Guardar
+doCancel=Cancelar
+doLogOutAllSessions=Desconectar de todas las sesiones
+doRemove=Eliminar
+doAdd=A\u00F1adir
+doSignOut=Desconectar
+
+editAccountHtmlTtile=Editar cuenta
+federatedIdentitiesHtmlTitle=Identidades federadas
+accountLogHtmlTitle=Registro de la cuenta
+changePasswordHtmlTitle=Cambiar contrase\u00F1a
+sessionsHtmlTitle=Sesiones
+accountManagementTitle=Gesti\u00F3n de Cuenta Keycloak
+authenticatorTitle=Autenticador
+applicationsHtmlTitle=Aplicaciones
+
+authenticatorCode=C\u00F3digo de un solo uso
+email=Email
+firstName=Nombre
+givenName=Nombre de pila
+fullName=Nombre completo
+lastName=Apellidos
+familyName=Apellido
+password=Contrase\u00F1a
+passwordConfirm=Confirma la contrase\u00F1a
+passwordNew=Nueva contrase\u00F1a
+username=Usuario
+address=Direcci\u00F3n
+street=Calle
+locality=Ciudad o Municipio
+region=Estado, Provincia, o Regi\u00F3n
+postal_code=C\u00F3digo Postal
+country=Pa\u00EDs
+emailVerified=Email verificado
+gssDelegationCredential=GSS Delegation Credential
+
+role_admin=Administrador
+role_realm-admin=Administrador del dominio
+role_create-realm=Crear dominio
+role_view-realm=Ver dominio
+role_view-users=Ver usuarios
+role_view-applications=Ver aplicaciones
+role_view-clients=Ver clientes
+role_view-events=Ver eventos
+role_view-identity-providers=Ver proveedores de identidad
+role_manage-realm=Gestionar dominio
+role_manage-users=Gestionar usuarios
+role_manage-applications=Gestionar aplicaciones
+role_manage-identity-providers=Gestionar proveedores de identidad
+role_manage-clients=Gestionar clientes
+role_manage-events=Gestionar eventos
+role_view-profile=Ver perfil
+role_manage-account=Gestionar cuenta
+role_read-token=Leer token
+role_offline-access=Acceso sin conexi\u00F3n
+client_account=Cuenta
+client_security-admin-console=Consola de Administraci\u00F3n de Seguridad
+client_realm-management=Gesti\u00F3n de dominio
+client_broker=Broker
+
+
+requiredFields=Campos obligatorios
+allFieldsRequired=Todos los campos obligatorios
+
+backToApplication=&laquo; Volver a la aplicaci\u00F3n
+backTo=Volver a {0}
+
+date=Fecha
+event=Evento
+ip=IP
+client=Cliente
+clients=Clientes
+details=Detalles
+started=Iniciado
+lastAccess=\u00DAltimo acceso
+expires=Expira
+applications=Aplicaciones
+
+account=Cuenta
+federatedIdentity=Identidad federada
+authenticator=Autenticador
+sessions=Sesiones
+log=Regisro
+
+application=Aplicaci\u00F3n
+availablePermissions=Permisos disponibles
+grantedPermissions=Permisos concedidos
+grantedPersonalInfo=Informaci\u00F3n personal concedida
+additionalGrants=Permisos adicionales
+action=Acci\u00F3n
+inResource=en
+fullAccess=Acceso total
+offlineToken=C\u00F3digo de autorizaci\u00F3n offline
+revoke=Revocar permiso
+
+configureAuthenticators=Autenticadores configurados
+mobile=M\u00F3vil
+totpStep1=Instala <a href=\"https://fedorahosted.org/freeotp/\" target=\"_blank\">FreeOTP</a> o Google Authenticator en tu tel\u00E9fono m\u00F3vil. Ambas aplicaciones est\u00E1n disponibles en <a href=\"https://play.google.com\">Google Play</a> y en la App Store de Apple.
+totpStep2=Abre la aplicacvi\u00F3n y escanea el c\u00F3digo o introduce la clave.
+totpStep3=Introduce el c\u00F3digo \u00FAnico que te muestra la aplicaci\u00F3n de autenticaci\u00F3n y haz clic en Enviar para finalizar la configuraci\u00F3n
+
+missingUsernameMessage=Por favor indica tu usuario.
+missingFirstNameMessage=Por favor indica el nombre.
+invalidEmailMessage=Email no v\u00E1lido
+missingLastNameMessage=Por favor indica tus apellidos.
+missingEmailMessage=Por favor indica el email.
+missingPasswordMessage=Por favor indica tu contrase\u00F1a.
+notMatchPasswordMessage=Las contrase\u00F1as no coinciden.
+
+missingTotpMessage=Por favor indica tu c\u00F3digo de autenticaci\u00F3n
+invalidPasswordExistingMessage=La contrase\u00F1a actual no es correcta.
+invalidPasswordConfirmMessage=La confirmaci\u00F3n de contrase\u00F1a no coincide.
+invalidTotpMessage=El c\u00F3digo de autenticaci\u00F3n no es v\u00E1lido.
+
+usernameExistsMessage=El usuario ya existe
+emailExistsMessage=El email ya existe
+
+readOnlyUserMessage=No puedes actualizar tu usuario porque tu cuenta es de solo lectura.
+readOnlyPasswordMessage=No puedes actualizar tu contrase\u00F1a porque tu cuenta es de solo lectura.
+
+successTotpMessage=Aplicaci\u00F3n de autenticaci\u00F3n m\u00F3vil configurada.
+successTotpRemovedMessage=Aplicaci\u00F3n de autenticaci\u00F3n m\u00F3vil eliminada.
+
+successGrantRevokedMessage=Permiso revocado correctamente
+
+accountUpdatedMessage=Tu cuenta se ha actualizado.
+accountPasswordUpdatedMessage=Tu contrase\u00F1a se ha actualizado.
+
+missingIdentityProviderMessage=Proveedor de identidad no indicado.
+invalidFederatedIdentityActionMessage=Acci\u00F3n no v\u00E1lida o no indicada.
+identityProviderNotFoundMessage=No se encontr\u00F3 un proveedor de identidad.
+federatedIdentityLinkNotActiveMessage=Esta identidad ya no est\u00E1 activa
+federatedIdentityRemovingLastProviderMessage=No puedes eliminar la \u00FAltima identidad federada porque no tienes fijada una contrase\u00F1a.
+identityProviderRedirectErrorMessage=Error en la redirecci\u00F3n al proveedor de identidad
+identityProviderRemovedMessage=Proveedor de identidad borrado correctamente.
+
+accountDisabledMessage=La cuenta est\u00E1 desactivada, contacta con el administrador.
+
+accountTemporarilyDisabledMessage=La cuenta est\u00E1 temporalmente desactivada, contacta con el administrador o int\u00E9ntalo de nuevo m\u00E1s tarde.
+invalidPasswordMinLengthMessage=Contrase\u00F1a incorrecta: longitud m\u00EDnima {0}.
+invalidPasswordMinLowerCaseCharsMessage=Contrase\u00F1a incorrecta: debe contener al menos {0} letras min\u00FAsculas.
+invalidPasswordMinDigitsMessage=Contrase\u00F1a incorrecta: debe contener al menos {0} caracteres num\u00E9ricos.
+invalidPasswordMinUpperCaseCharsMessage=Contrase\u00F1a incorrecta: debe contener al menos {0} letras may\u00FAsculas.
+invalidPasswordMinSpecialCharsMessage=Contrase\u00F1a incorrecta: debe contener al menos {0} caracteres especiales.
+invalidPasswordNotUsernameMessage=Contrase\u00F1a incorrecta: no puede ser igual al nombre de usuario.
+invalidPasswordRegexPatternMessage=Contrase\u00F1a incorrecta: no cumple la expresi\u00F3n regular.
+invalidPasswordHistoryMessage=Contrase\u00F1a incorrecta: no puede ser igual a ninguna de las \u00FAltimas {0} contrase\u00F1as.
+
+locale_de=German
+locale_en=English
+locale_it=Italian
+locale_pt-BR=Portugu\u00EAs (Brasil)
+locale_fr=Fran\u00E7ais
+locale_es=Espa\u00F1ol
diff --git a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_fr.properties b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_fr.properties
index 6831687..56e41d4 100644
--- a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_fr.properties
+++ b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_fr.properties
@@ -10,7 +10,7 @@ doSignOut=D\u00e9connexion
 
 editAccountHtmlTtile=Edition du compte
 federatedIdentitiesHtmlTitle=Identit\u00e9s f\u00e9d\u00e9r\u00e9es
-accountLogHtmlTitle=Acces au compte
+accountLogHtmlTitle=Acc\u00e8s au compte
 changePasswordHtmlTitle=Changer de mot de passe
 sessionsHtmlTitle=Sessions
 accountManagementTitle=Gestion de Compte Keycloak
@@ -22,7 +22,7 @@ email=Courriel
 firstName=Nom
 givenName=Pr\u00e9nom
 fullName=Nom Complet
-lastName=Last name
+lastName=Nom
 familyName=Nom de Famille
 password=Mot de passe
 passwordConfirm=Confirmation
@@ -31,7 +31,7 @@ username=Compte
 address=Adresse
 street=Rue
 locality=Ville ou Localit\u00e9
-region=State, Province, or R\u00e9gion
+region=\u00c9tat, Province ou R\u00e9gion
 postal_code=Code Postal
 country=Pays
 emailVerified=Courriel v\u00e9rifi\u00e9
@@ -50,7 +50,7 @@ role_manage-realm=G\u00e9rer le domaine
 role_manage-users=G\u00e9rer les utilisateurs
 role_manage-applications=G\u00e9rer les applications
 role_manage-identity-providers=G\u00e9rer les fournisseurs d''identit\u00e9s
-role_manage-clients=G\u00e9rer les  clients
+role_manage-clients=G\u00e9rer les clients
 role_manage-events=G\u00e9rer les  \u00e9v\u00e9nements
 role_view-profile=Voir le profile
 role_manage-account=G\u00e9rer le compte
@@ -63,7 +63,7 @@ client_broker=Broker
 
 
 requiredFields=Champs obligatoires
-allFieldsRequired=Tous les champs obligatoires
+allFieldsRequired=Tous les champs sont obligatoires
 
 backToApplication=&laquo; Revenir \u00e0 l''application
 backTo=Revenir \u00e0 {0}
@@ -74,9 +74,9 @@ ip=IP
 client=Client
 clients=Clients
 details=D\u00e9tails
-started=S\u00e9lectionn\u00e9
+started=D\u00e9but
 lastAccess=Dernier acc\u00e8s
-expires=Expires
+expires=Expiration
 applications=Applications
 
 account=Compte
@@ -88,7 +88,7 @@ log=Connexion
 application=Application
 availablePermissions=Permissions Disponibles
 grantedPermissions=Permissions accord\u00e9es
-grantedPersonalInfo=Informations personnels accord\u00e9es
+grantedPersonalInfo=Informations personnelles accord\u00e9es
 additionalGrants=Droits additionnels
 action=Action
 inResource=dans
@@ -99,7 +99,7 @@ revoke=R\u00e9voquer un droit
 configureAuthenticators=Authentifications configur\u00e9es.
 mobile=T\u00e9l\u00e9phone mobile
 totpStep1=Installez <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> ou bien Google Authenticator sur votre mobile. Ces deux applications sont disponibles sur <a href="https://play.google.com">Google Play</a> et Apple App Store.
-totpStep2=Ouvrez l''application et scanner le code bar ou entrez la clef.
+totpStep2=Ouvrez l''application et scanner le code barre ou entrez la cl\u00e9.
 totpStep3=Entrez le code \u00e0 usage unique fourni par l''application et cliquez sur Sauvegarder pour terminer.
 
 missingUsernameMessage=Veuillez entrer votre nom d''utilisateur.
@@ -140,17 +140,18 @@ identityProviderRemovedMessage=Le fournisseur d''identit\u00e9 a \u00e9t\u00e9 s
 accountDisabledMessage=Ce compte est d\u00e9sactiv\u00e9, veuillez contacter votre administrateur.
 
 accountTemporarilyDisabledMessage=Ce compte est temporairement d\u00e9sactiv\u00e9, veuillez contacter votre administrateur ou r\u00e9essayez plus tard..
-invalidPasswordMinLengthMessage=Mot de passe invalide : longueur minimale {0}.
-invalidPasswordMinLowerCaseCharsMessage=Mot de passe invalide : doit contenir au moins {0} lettre(s) en minuscule.
-invalidPasswordMinDigitsMessage=Mot de passe invalide : doit contenir au moins {0} chiffre(s).
-invalidPasswordMinUpperCaseCharsMessage=Mot de passe invalide : doit contenir au moins {0} lettre(s) en majuscule.
-invalidPasswordMinSpecialCharsMessage=Mot de passe invalide : doit contenir au moins {0} caract\u00e8re(s) sp\u00e9ciaux.
-invalidPasswordNotUsernameMessage=Mot de passe invalide : ne doit pas etre identique au nom d''utilisateur.
-invalidPasswordRegexPatternMessage=Mot de passe invalide : ne valide pas l''expression rationnelle.
-invalidPasswordHistoryMessage=Mot de passe invalide : ne doit pas \u00eatre \u00e9gal aux {0} derniers mot de passe.
+invalidPasswordMinLengthMessage=Mot de passe invalide: longueur minimale {0}.
+invalidPasswordMinLowerCaseCharsMessage=Mot de passe invalide: doit contenir au moins {0} lettre(s) en minuscule.
+invalidPasswordMinDigitsMessage=Mot de passe invalide: doit contenir au moins {0} chiffre(s).
+invalidPasswordMinUpperCaseCharsMessage=Mot de passe invalide: doit contenir au moins {0} lettre(s) en majuscule.
+invalidPasswordMinSpecialCharsMessage=Mot de passe invalide: doit contenir au moins {0} caract\u00e8re(s) sp\u00e9ciaux.
+invalidPasswordNotUsernameMessage=Mot de passe invalide: ne doit pas \u00eatre identique au nom d''utilisateur.
+invalidPasswordRegexPatternMessage=Mot de passe invalide: ne valide pas l''expression rationnelle.
+invalidPasswordHistoryMessage=Mot de passe invalide: ne doit pas \u00eatre \u00e9gal aux {0} derniers mot de passe.
 
 locale_de=German
 locale_en=English
 locale_it=Italian
 locale_pt-BR=Portugu\u00EAs (Brasil)
 locale_fr=Fran\u00e7ais
+locale_es=Espa\u00F1ol
diff --git a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_it.properties b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_it.properties
index da7e9d0..2aa902a 100755
--- a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_it.properties
+++ b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_it.properties
@@ -125,4 +125,4 @@ locale_de=German
 locale_en=English
 locale_it=Italian
 locale_pt-BR=Portugu\u00EAs (Brasil)
-locale_fr=Fran�ais
\ No newline at end of file
+locale_fr=Fran\u00e7ais
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_pt_BR.properties b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_pt_BR.properties
index a569cfc..37c0106 100644
--- a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_pt_BR.properties
+++ b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_pt_BR.properties
@@ -147,4 +147,4 @@ locale_de=Deutsch
 locale_en=English
 locale_it=Italian
 locale_pt-BR=Portugu\u00EAs (BR)
-locale_fr=Fran�ais
\ No newline at end of file
+locale_fr=Fran\u00e7ais
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/index.ftl b/forms/common-themes/src/main/resources/theme/base/admin/index.ftl
index 1584d0d..0cdadfb 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/index.ftl
+++ b/forms/common-themes/src/main/resources/theme/base/admin/index.ftl
@@ -25,6 +25,7 @@
     <script src="${resourceUrl}/lib/angular/angular-sanitize.js"></script>
     <script src="${resourceUrl}/lib/angular/angular-translate.js"></script>
     <script src="${resourceUrl}/lib/angular/angular-translate-loader-url.js"></script>
+    <script src="${resourceUrl}/lib/angular/treeview/angular.treeview.js"></script>
     <script src="${resourceUrl}/lib/angular/ui-bootstrap-tpls-0.11.0.js"></script>
 
     <script src="${resourceUrl}/lib/angular/select2.js" type="text/javascript"></script>
@@ -37,6 +38,7 @@
     <script src="${resourceUrl}/js/controllers/realm.js" type="text/javascript"></script>
     <script src="${resourceUrl}/js/controllers/clients.js" type="text/javascript"></script>
     <script src="${resourceUrl}/js/controllers/users.js" type="text/javascript"></script>
+    <script src="${resourceUrl}/js/controllers/groups.js" type="text/javascript"></script>
     <script src="${resourceUrl}/js/loaders.js" type="text/javascript"></script>
     <script src="${resourceUrl}/js/services.js" type="text/javascript"></script>
 </head>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_de.properties b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_de.properties
index 87640e0..295a8ce 100644
--- a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_de.properties
+++ b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_de.properties
@@ -22,7 +22,7 @@ registrationEmailAsUsername=de Email as username
 registrationEmailAsUsername.tooltip=de If enabled then username field is hidden from registration form and email is used as username for new user.
 editUsernameAllowed=de Edit username
 editUsernameAllowed.tooltip=de If enabled, the username field is editable, readonly otherwise.
-resetPasswordAllowed=de Forget password
+resetPasswordAllowed=de Forgot password
 resetPasswordAllowed.tooltip=de Show a link on login page for user to click on when they have forgotten their credentials.
 rememberMe=de Remember Me
 rememberMe.tooltip=de Show checkbox on login page to allow user to remain logged in between browser restarts until session expires.
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index f13d6e8..e274ca7 100644
--- a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -22,7 +22,7 @@ registrationEmailAsUsername=Email as username
 registrationEmailAsUsername.tooltip=If enabled then username field is hidden from registration form and email is used as username for new user.
 editUsernameAllowed=Edit username
 editUsernameAllowed.tooltip=If enabled, the username field is editable, readonly otherwise.
-resetPasswordAllowed=Forget password
+resetPasswordAllowed=Forgot password
 resetPasswordAllowed.tooltip=Show a link on login page for user to click on when they have forgotten their credentials.
 rememberMe=Remember Me
 rememberMe.tooltip=Show checkbox on login page to allow user to remain logged in between browser restarts until session expires.
@@ -109,6 +109,7 @@ realm-tab-email=Email
 realm-tab-themes=Themes
 realm-tab-cache=Cache
 realm-tab-tokens=Tokens
+realm-tab-client-initial-access=Initial Access Tokens
 realm-tab-security-defenses=Security Defenses
 realm-tab-general=General
 add-realm=Add Realm
@@ -271,6 +272,9 @@ import-client-certificate=Import Client Certificate
 jwt-import.key-alias.tooltip=Archive alias for your certificate.
 secret=Secret
 regenerate-secret=Regenerate Secret
+registrationAccessToken=Registration access token
+registrationAccessToken.regenerate=Regenerate registration access token
+registrationAccessToken.tooltip=The registration access token provides access for clients to the client registration service.
 add-role=Add Role
 role-name=Role Name
 composite=Composite
@@ -374,6 +378,7 @@ table-of-identity-providers=Table of identity providers
 add-provider.placeholder=Add provider...
 provider=Provider
 gui-order=GUI order
+first-broker-login-flow=First Login Flow
 redirect-uri=Redirect URI
 redirect-uri.tooltip=The redirect uri to use when configuring the identity provider.
 alias=Alias
@@ -393,6 +398,7 @@ update-profile-on-first-login.tooltip=Define conditions under which a user has t
 trust-email=Trust Email
 trust-email.tooltip=If enabled then email provided by this provider is not verified even if verification is enabled for the realm.
 gui-order.tooltip=Number defining order of the provider in GUI (eg. on Login page).
+first-broker-login-flow.tooltip=Alias of authentication flow, which is triggered after first login with this identity provider. Term 'First Login' means that there is not yet existing Keycloak account linked with the authenticated identity provider account.
 openid-connect-config=OpenID Connect Config
 openid-connect-config.tooltip=OIDC SP and external IDP configuration.
 authorization-url=Authorization URL
@@ -465,3 +471,17 @@ identity-provider-mappers=Identity Provider Mappers
 create-identity-provider-mapper=Create Identity Provider Mapper
 add-identity-provider-mapper=Add Identity Provider Mapper
 client.description.tooltip=Specifies description of the client. For example 'My Client for TimeSheets'. Supports keys for localized values as well. For example\: ${my_client_description}
+
+expires=Expires
+expiration=Expiration
+count=Count
+remainingCount=Remaining count
+created=Created
+back=Back
+initial-access-tokens=Initial Access Tokens
+add-initial-access-tokens=Add Initial Access Token
+initial-access-token=Initial Access Token
+initial-access.copyPaste.tooltip=Copy/paste the initial access token before navigating away from this page as it's not posible to retrieve later
+continue=Continue
+initial-access-token.confirm.title=Copy Initial Access Token
+initial-access-token.confirm.text=Please copy and paste the initial access token before confirming as it can't be retrieved later
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_es.properties b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_es.properties
new file mode 100644
index 0000000..170720a
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_es.properties
@@ -0,0 +1,466 @@
+# Common messages
+enabled=Habilitado
+name=Nombre
+save=Guardar
+cancel=Cancelar
+onText=SI
+offText=NO
+client=Cliente
+clients=Clientes
+clear=Limpiar
+selectOne=Selecciona uno...
+
+true=S\u00ED
+false=No
+
+
+# Realm settings
+realm-detail.enabled.tooltip=Los usuarios y clientes solo pueden acceder a un dominio si est\u00E1 habilitado
+registrationAllowed=Registro de usuario
+registrationAllowed.tooltip=Habilitar/deshabilitar la p\u00E1gina de registro. Un enlace para el registro se motrar\u00E1 tambi\u00E9n en la p\u00E1gina de inicio de sesi\u00F3n.
+registrationEmailAsUsername=Email como nombre de usuario
+registrationEmailAsUsername.tooltip=Si est\u00E1 habilitado el nombre de usuario queda oculto del formulario de registro y el email se usa como nombre de usuario para los nuevos usuarios.
+editUsernameAllowed=Editar nombre de usuario
+editUsernameAllowed.tooltip=Si est\u00E1 habilitado, el nombre de usuario es editable, en otro caso es de solo lectura.
+resetPasswordAllowed=Olvido contrase\u00F1a
+resetPasswordAllowed.tooltip=Muestra un enlace en la p\u00E1gina de inicio de sesi\u00F3n para que el usuario haga clic cuando ha olvidado sus credenciales.
+rememberMe=Seguir conectado
+rememberMe.tooltip=Muestra la casilla de selecci\u00F3n en la p\u00E1gina de inicio de sesi\u00F3n para permitir al usuario permanecer conectado entre reinicios del navegador hasta que la sesi\u00F3n expire.
+verifyEmail=Verificar email
+verifyEmail.tooltip=Forzar al usuario a verificar su direcci\u00F3n de email la primera vez que inicie sesi\u00F3n.
+sslRequired=Solicitar SSL
+sslRequired.option.all=todas las peticiones
+sslRequired.option.external=peticiones externas
+sslRequired.option.none=ninguna
+sslRequired.tooltip=\u00BFEs HTTP obligatorio? 'ninguna' significa que HTTPS no es obligatorio para ninguna direcic\u00F3n IP de cliente, 'peticiones externas' indica que localhost y las direcciones IP privadas pueden acceder sin HTTPS, 'todas las peticiones' significa que HTTPS es obligatorio para todas las direcciones IP.
+publicKey=Clave p\u00FAblica
+gen-new-keys=Generar nuevas claves
+certificate=Certificado
+host=Host
+smtp-host=Host SMTP
+port=Puerto
+smtp-port=Puerto SMTP (por defecto 25)
+from=De
+sender-email-addr=Email del emisor
+enable-ssl=Habilitar SSL
+enable-start-tls=Habilitar StartTLS
+enable-auth=Habilitar autenticaci\u00F3n
+username=Usuario
+login-username=Usuario
+password=Contrase\u00F1a
+login-password=Contrase\u00F1a
+login-theme=Tema de inicio de sesi\u00F3n
+select-one=Selecciona uno...
+login-theme.tooltip=Selecciona el tema para las p\u00E1gina de inicio de sesi\u00F3n, TOTP, permisos, registro y recordatorio de contrase\u00F1a.
+account-theme=Tema de cuenta
+account-theme.tooltip=Selecciona el tema para las p\u00E1ginas de gesti\u00F3n de la cuenta de usuario.
+admin-console-theme=Tema de consola de administraci\u00F3n
+select-theme-admin-console=Selecciona el tema para la consola de administraci\u00F3n.
+email-theme=Tema de email
+select-theme-email=Selecciona el tema para los emails que son enviados por el servidor.
+i18n-enabled=Internacionalizaci\u00F3n activa
+supported-locales=Idiomas soportados
+supported-locales.placeholder=Indica el idioma y pulsa Intro
+default-locale=Idioma por defecto
+realm-cache-enabled=Cach\u00E9 de dominio habilitada
+realm-cache-enabled.tooltip=Activar/desactivar la cach\u00E9 para el dominio, cliente y datos de roles.
+user-cache-enabled=Cach\u00E9 de usuario habilitada
+user-cache-enabled.tooltip=Habilitar/deshabilitar la cach\u00E9 de usuarios y de asignaciones de usuarios a roles.
+revoke-refresh-token=Revocar el token de actualizaci\u00F3n
+revoke-refresh-token.tooltip=Si est\u00E1 activado los tokens de actualizaci\u00F3n solo pueden usarse una vez. En otro caso los tokens de actualizaci\u00F3n no se revocan cuando se utilizan y pueden ser usado m\u00FAltiples veces.
+sso-session-idle=Sesiones SSO inactivas
+seconds=Segundos
+minutes=Minutos
+hours=Horas
+days=D\u00EDas
+sso-session-max=Tiempo m\u00E1ximo sesi\u00F3n SSO
+sso-session-idle.tooltip=Tiempo m\u00E1ximo que una sesi\u00F3n puede estar inactiva antes de que expire. Los tokens y sesiones de navegador son invalidadas cuando la sesi\u00F3n expira.
+sso-session-max.tooltip=Tiempo m\u00E1ximo antes de que una sesi\u00F3n expire. Los tokesn y sesiones de navegador son invalidados cuando una sesi\u00F3n expira.
+offline-session-idle=Inactividad de sesi\u00F3n sin conexi\u00F3n
+offline-session-idle.tooltip=Tiempo m\u00E1ximo inactivo de una sesi\u00F3n sin conexi\u00F3n antes de que expire. Necesitas usar un token sin conexi\u00F3n para refrescar al menos una vez dentro de este periodo, en otro caso la sesi\u00F3n sin conexi\u00F3n expirar\u00E1.
+access-token-lifespan=Duraci\u00F3n del token de acceso
+access-token-lifespan.tooltip=Tiempo m\u00E1ximo antes de que un token de acceso expire. Se recomiena que esta valor sea corto en relaci\u00F3n al tiempo m\u00E1ximo de SSO
+client-login-timeout=Tiempo m\u00E1ximo de autenticaci\u00F3n
+client-login-timeout.tooltip=Tiempo m\u00E1ximo que un cliente tien para finalizar el protocolo de obtenci\u00F3n del token de acceso. Deber\u00EDa ser normalmente del orden de 1 minuto.
+login-timeout=Tiempo m\u00E1ximo de desconexi\u00F3n
+login-timeout.tooltip=Tiempo m\u00E1xmo que un usuario tiene para completar el inicio de sesi\u00F3n. Se recomienda que sea relativamente alto. 30 minutos o m\u00E1s.
+login-action-timeout=Tiempo m\u00E1ximo de acci\u00F3n en el inicio de sesi\u00F3n
+login-action-timeout.tooltip=Tiempo m\u00E1ximo que un usuario tiene para completar acciones relacionadas con el inicio de sesi\u00F3n, como la actualizaci\u00F3n de contrase\u00F1a o configuraci\u00F3n de TOTP. Es recomendado que sea relativamente alto. 5 minutos o m\u00E1s.
+headers=Cabeceras
+brute-force-detection=Detecci\u00F3n de ataques por fuerza bruta
+x-frame-options=X-Frame-Options
+click-label-for-info=Haz clic en el enlace de la etiqueta para obtener m\u00E1s informaci\u00F3n. El valor por defecto evita que las p\u00E1ginas sean incluidaos desde iframes externos.
+content-sec-policy=Content-Security-Policy
+max-login-failures=N\u00FAmero m\u00E1ximo de fallos de inicios de sesi\u00F3n
+max-login-failures.tooltip=Indica cuantos fallos se permiten antes de que se dispare una espera.
+wait-increment=Incremento de espera
+wait-increment.tooltip=Cuando se ha alcanzado el umbral de fallo, \u00BFcuanto tiempo debe estar un usuario bloqueado?
+quick-login-check-millis=Tiempo en milisegundos entre inicios de sesi\u00F3n r\u00E1pidos
+quick-login-check-millis.tooltip=Si ocurren errores de forma concurrente y muy r\u00E1pida, bloquear al usuario.
+min-quick-login-wait=Tiempo m\u00EDnimo entre fallos de conexi\u00F3n r\u00E1pidos
+min-quick-login-wait.tooltip=Cuanto tiempo se debe esperar tras un fallo en un intento r\u00E1pido de identificaci\u00F3n
+max-wait=Espera m\u00E1xima
+max-wait.tooltip=Tiempo m\u00E1ximo que un usuario quedar\u00E1 bloqueado.
+failure-reset-time=Reinicio del contador de errores
+failure-reset-time.tooltip=\u00BFCuando se debe reiniciar el contador de errores?
+realm-tab-login=Inicio de sesi\u00F3n
+realm-tab-keys=Claves
+realm-tab-email=Email
+realm-tab-themes=Temas
+realm-tab-cache=Cache
+realm-tab-tokens=Tokens
+realm-tab-security-defenses=Defensas de seguridad
+realm-tab-general=General
+add-realm=A\u00F1adir dominio
+
+#Session settings
+realm-sessions=Sesiones de dominio
+revocation=Revocaci\u00F3n
+logout-all=Desconectar todo
+active-sessions=Sesiones activas
+sessions=Sesiones
+not-before=No antes de
+not-before.tooltip=Revocar cualquier token emitido antes de esta fecha.
+set-to-now=Fijar a ahora
+push=Push
+push.tooltip=Para cada cliente que tiene una URL de administraci\u00F3n, notificarlos the las nuevas pol\u00EDticas de revocaci\u00F3n.
+
+#Protocol Mapper
+usermodel.prop.label=Propiedad
+usermodel.prop.tooltip=Nombre del m\u00E9todo de propiedad in la interfaz UserModel. Por ejemplo, un valor de 'email' referenciar\u00EDa al m\u00E9todo UserModel.getEmail().
+usermodel.attr.label=Atributo de usuario
+usermodel.attr.tooltip=Nombre del atributo de usuario almacenado que es el nombre del atributo dentro del map UserModel.attribute.
+userSession.modelNote.label=Nota sesi\u00F3n usuario
+userSession.modelNote.tooltip=Nombre de la nota almacenada en la sesi\u00F3n de usuario dentro del mapa UserSessionModel.note
+multivalued.label=Valores m\u00FAltiples
+multivalued.tooltip=Indica si el atributo soporta m\u00FAltiples valores. Si est\u00E1 habilitado, la lista de todos los valores de este atributo se fijar\u00E1 como reclamaci\u00F3n. Si est\u00E1 deshabilitado, solo el primer valor ser\u00E1 fijado como reclamaci\u00F3n.
+selectRole.label=Selecciona rol
+selectRole.tooltip=Introduce el rol en la caja de texto de la izquierda, o haz clic en este bot\u00F3n para navegar y buscar el rol que quieres.
+tokenClaimName.label=Nombre de reclamo del token
+tokenClaimName.tooltip=Nombre del reclamo a insertar en el token. Puede ser un nombre completo como 'address.street'. En este caso, se crear\u00E1 un objeto JSON anidado.
+jsonType.label=Tipo JSON de reclamaci\u00F3n
+jsonType.tooltip=El tipo de JSON que deber\u00EDa ser usado para rellenar la petici\u00F3n de JSON en el token. long, int, boolean y String son valores v\u00E1lidos
+includeInIdToken.label=A\u00F1adir al token de ID
+includeInAccessToken.label=A\u00F1adir al token de acceso
+includeInAccessToken.tooltip=\u00BFDeber\u00EDa a\u00F1adirse la identidad reclamada al token de acceso?
+
+
+# client details
+clients.tooltip=Los clientes son aplicaciones de navegador de confianza y servicios web de un dominio. Estos clientes pueden solicitar un inicio de sesi\u00F3n. Tambi\u00E9n puedes definir roles espec\u00EDficos de cliente.
+search.placeholder=Buscar...
+create=Crear
+import=Importar
+client-id=ID Cliente
+base-url=URL Base
+actions=Acciones
+not-defined=No definido
+edit=Editar
+delete=Borrar
+no-results=Sin resultados
+no-clients-available=No hay clientes disponibles
+add-client=A\u00F1adir Cliente
+select-file=Selecciona archivo
+view-details=Ver detalles
+clear-import=Limpiar importaci\u00F3n
+client-id.tooltip=Indica el identificador (ID) referenciado en URIs y tokens. Por ejemplo 'my-client'
+client.name.tooltip=Indica el nombre visible del cliente. Por ejemplo 'My Client'. Tambi\u00E9n soporta claves para vallores localizados. Por ejemplo: ${my_client}
+client.enabled.tooltip=Los clientes deshabilitados no pueden iniciar una identificaci\u00F3n u obtener c\u00F3digos de acceso.
+consent-required=Consentimiento necesario
+consent-required.tooltip=Si est\u00E1 habilitado, los usuarios tienen que consentir el acceso del cliente.
+direct-grants-only=Solo permisos directos
+direct-grants-only.tooltip=Cuando est\u00E1 habilitado, el cliente solo puede obtener permisos de la API REST.
+client-protocol=Protocolo del Cliente
+client-protocol.tooltip='OpenID connect' permite a los clientes verificar la identidad del usuario final basado en la autenticaci\u00F3n realizada por un servidor de autorizaci\u00F3n. 'SAML' habilita la autenticaci\u00F3n y autorizaci\u00F3n de escenarios basados en web incluyendo cross-domain y single sign-on (SSO) y utiliza tokdne de seguridad que contienen afirmaciones para pasar informaci\u00F3n.
+access-type=Tipo de acceso
+access-type.tooltip=Los clientes 'Confidential' necesitan un secreto para iniciar el protocolo de identificaci\u00F3n. Los clientes 'Public' no requieren un secreto. Los clientes 'Bearer-only' son servicios web que nunca inician un login.
+service-accounts-enabled=Cuentas de servicio habilitadas
+service-accounts-enabled.tooltip=Permitir autenticar este cliente contra Keycloak y recivir un token de acceso dedicado para este cliente.
+include-authnstatement=Incluir AuthnStatement
+include-authnstatement.tooltip=null
+sign-documents=Firmar documentos
+sign-documents.tooltip=\u00BFDeber\u00EDa el dominio firmar los documentos SAML?
+sign-assertions=Firmar aserciones
+sign-assertions.tooltip=\u00BFDeber\u00EDan firmarse las aserciones en documentos SAML? Este ajuste no es necesario si el documento ya est\u00E1 siendo firmado.
+signature-algorithm=Algoritmo de firma
+signature-algorithm.tooltip=El algoritmo de firma usado para firmar los documentos.
+canonicalization-method=M\u00E9todo de canonicalizaci\u00F3n
+canonicalization-method.tooltip=M\u00E9todo de canonicalizaci\u00F3n para las firmas XML
+encrypt-assertions=Cifrar afirmaciones
+encrypt-assertions.tooltip=\u00BFDeber\u00EDan cifrarse las afirmaciones SAML con la clave p\u00FAblica del cliente usando AES?
+client-signature-required=Firma de Cliente requerida
+client-signature-required.tooltip=\u00BFFirmar\u00E1 el cliente sus peticiones y respuestas SAML? \u00BFY deber\u00EDan ser validadas?
+force-post-binding=Forzar enlaces POST
+force-post-binding.tooltip=Usar siempre POST para las respuestas
+front-channel-logout=Desonexi\u00F3n en primer plano (Front Channel)
+front-channel-logout.tooltip=Cuando est\u00E1 activado, la desconexi\u00F3n require una redirecci\u00F3n del navegador hacia el cliente. Cuando no est\u00E1 activado, el servidor realiza una invovaci\u00F3n de desconexi\u00F3n en segundo plano.
+force-name-id-format=Forzar formato NameID
+force-name-id-format.tooltip=Ignorar la petici\u00F3n de sujeto NameID y usar la configurada en la consola de administraci\u00F3n.
+name-id-format=Formato de NameID
+name-id-format.tooltip=El formato de NameID que se usar\u00E1 para el t\u00EDtulo
+root-url=URL ra\u00EDz
+root-url.tooltip=URL ra\u00EDz a\u00F1adida a las URLs relativas
+valid-redirect-uris=URIs de redirecci\u00F3n v\u00E1lidas
+valid-redirect-uris.tooltip=Patr\u00F3n de URI v\u00E1lida para la cual un navegador puede solicitar la redirecci\u00F3n tras un inicio o cierre de sesi\u00F3n completado. Se permiten comodines simples p.ej. 'http://example.com/*'. Tambi\u00E9n se pueden indicar rutas relativas p.ej. '/my/relative/path/*'. Las rutas relativas generar\u00E1n una URI de redirecci\u00F3n usando el host y puerto de la petici\u00F3n. Para SAML, se deben fijar patrones de URI v\u00E1lidos si quieres confiar en la URL del servicio del consumidor indicada en la petici\u00F3n de inicio de sesi\u00F3n.
+base-url.tooltip=URL por defecto para usar cuando el servidor de autorizaci\u00F3n necesita redirigir o enviar de vuelta al cliente.
+admin-url=URL de administraci\u00F3n
+admin-url.tooltip=URL a la interfaz de administraci\u00F3n del cliente. Fija este valor si el cliente soporta el adaptador de REST. Esta API REST permite al servidor de autenticaci\u00F3n enviar al cliente pol\u00EDticas de revocaci\u00F3n y otras tareas administrativas. Normalment se fija a la URL base del cliente.
+master-saml-processing-url=URL principal de procesamiento SAML
+master-saml-processing-url.tooltip=Si est\u00E1 configurada, esta URL se usar\u00E1 para cada enlace al proveedor del servicio del consumidor de aserciones y servicios de desconexi\u00F3n \u00FAnicos. Puede ser sobreescrito de forma individual para cada enlace y servicio en el punto final de configuraci\u00F3n fina de SAML.
+idp-sso-url-ref=Nombre de la URL de un SSO iniciado por el IDP
+idp-sso-url-ref.tooltip=Nombre del fragmento de la URL para referenciar al cliente cuando quieres un SSO iniciado por el IDP. Dejanto esto vac\u00EDo deshabilita los SSO iniciados por el IDP. La URL referenciada desde el navegador ser\u00E1: {server-root}/realms/{realm}/protocol/saml/clients/{client-url-name}
+idp-sso-relay-state=Estado de retransmisi\u00F3n de un SSO iniciado por el IDP
+idp-sso-relay-state.tooltip=Estado de retransmisi\u00F3n que quiees enviar con una petici\u00F3n SAML cuando se inicia un SSO inidicado por el IDP
+web-origins=Origenes web
+web-origins.tooltip=Origenes CORS permitidos. Para permitir todos los or\u00EDgenes de URIs de redirecci\u00F3n v\u00E1lidas a\u00F1ade '+'. Para permitir todos los or\u00EDgenes a\u00F1ade '*'.
+fine-saml-endpoint-conf=Fine Grain SAML Endpoint Configuration
+fine-saml-endpoint-conf.tooltip=Expande esta secci\u00F3n para configurar las URL exactas para Assertion Consumer y Single Logout Service.
+assertion-consumer-post-binding-url=Assertion Consumer Service POST Binding URL
+assertion-consumer-post-binding-url.tooltip=SAML POST Binding URL for the client's assertion consumer service (login responses).  You can leave this blank if you do not have a URL for this binding.
+assertion-consumer-redirect-binding-url=Assertion Consumer Service Redirect Binding URL
+assertion-consumer-redirect-binding-url.tooltip=Assertion Consumer Service Redirect Binding URL
+logout-service-binding-post-url=URL de enlace SAML POST para la desconexi\u00F3n
+logout-service-binding-post-url.tooltip=URL de enlace SAML POST para la desconexi\u00F3n \u00FAnica del cliente. Puedes dejar esto en blanco si est\u00E1s usando un enlace distinto.
+logout-service-redir-binding-url=URL de enlace SAML de redirecci\u00F3n para la desconexi\u00F3n
+logout-service-redir-binding-url.tooltip=URL de enlace SAML de redirecci\u00F3n para la desconexi\u00F3n \u00FAnica del cliente. Puedes dejar esto en blanco si est\u00E1s usando un enlace distinto.
+
+# client import
+import-client=Importar Cliente
+format-option=Formato
+select-format=Selecciona un formato
+import-file=Archivo de Importaci\u00F3n
+
+# client tabs
+settings=Ajustes
+credentials=Credenciales
+saml-keys=Claves SAML
+roles=Roles
+mappers=Asignadores
+mappers.tootip=Los asignadores de protocolos realizan transformaciones en tokens y documentos. Pueden hacer cosas como asignar datos de usuario en peticiones de protocolo, o simplemente transformar cualquier petici\u00F3n entre el cliente y el servidor de autenticaci\u00F3n.
+scope=\u00C1mbito
+scope.tooltip=Las asignaciones de \u00E1mbito te permiten restringir que asignaciones de roles de usuario se incluyen en el token de acceso solicitado por el cliente.
+sessions.tooltip=Ver sesiones activas para este cliente. Permite ver qu\u00E9 usuarios est\u00E1n activos y cuando se identificaron.
+offline-access=Acceso sin conexi\u00F3n
+offline-access.tooltip=Ver sesiones sin conexi\u00F3n para este cliente. Te permite ver que usuarios han solicitado tokens sin conexi\u00F3n y cuando los solicitaron. Para revocar todos los tokens del cliente, accede a la pesta\u00F1a de Revocaci\u00F3n y fija el valor \"No antes de\" a \"now\".
+clustering=Clustering
+installation=Instalaci\u00F3n
+installation.tooltip=Herramienta de ayuda para generar la configuraci\u00F3n de varios formatos de adaptadores de cliente que puedes descargar o copiar y pegar para configurar tus clientes.
+service-account-roles=Roles de cuenta de servicio
+service-account-roles.tooltip=Permitir autenticar asignaciones de rol para la cuenta de servicio dedicada a este cliente.
+
+# client credentials
+client-authenticator=Cliente autenticador
+client-authenticator.tooltip=Cliente autenticador usado para autenticar este cliente contra el servidor Keycloak
+certificate.tooltip=Certificado de clinete para validar los JWT emitidos por este cliente y firmados con la clave privada del cliente de tu almac\u00E9n de claves.
+no-client-certificate-configured=No se ha configurado el certificado de cliente
+gen-new-keys-and-cert=Genearr nuevas claves y certificado
+import-certificate=Importar Certificado
+gen-client-private-key=Generar clave privada de cliente
+generate-private-key=Generar clave privada
+archive-format=Formato de Archivo
+archive-format.tooltip=Formato de archivo Java keystore o PKCS12
+key-alias=Alias de clave
+key-alias.tooltip=Alias del archivo de tu clave privada y certificado.
+key-password=Contrase\u00F1a de la clave
+key-password.tooltip=Contrase\u00F1a para acceder a la clave privada contenida en el archivo
+store-password=Contrase\u00F1a del almac\u00E9n
+store-password.tooltip=Contrase\u00F1a para acceder al archivo
+generate-and-download=Generar y descargar
+client-certificate-import=Importaci\u00F3n de certificado de cliente
+import-client-certificate=Importar Certificado de Cliente
+jwt-import.key-alias.tooltip=Alias del archivo de tu certificado.
+secret=Secreto
+regenerate-secret=Regenerar secreto
+add-role=A\u00F1adir rol
+role-name=Nombre de rol
+composite=Compuesto
+description=Descripci\u00F3n
+no-client-roles-available=No hay roles de cliente disponibles
+scope-param-required=Par\u00E1metro de \u00E1mbito obligatorio
+scope-param-required.tooltip=Este rol solo ser\u00E1 concedido si el par\u00E1metro de \u00E1mbito con el nombre del rol es usado durante la petici\u00F3n de autorizaci\u00F3n/obtenci\u00F3n de token.
+composite-roles=Roles compuestos
+composite-roles.tooltip=Cuanto este rol es asignado/desasignado a un usuario cualquier rol asociado con \u00E9l ser\u00E1 asignado/desasignado de forma impl\u00EDcita.
+realm-roles=Roles de dominio
+available-roles=Roles Disponibles
+add-selected=A\u00F1adir seleccionado
+associated-roles=Roles Asociados
+composite.associated-realm-roles.tooltip=Roles a nivel de dominio asociados con este rol compuesto.
+composite.available-realm-roles.tooltip=Roles a nivel de dominio disponibles en este rol compuesto.
+remove-selected=Borrar seleccionados
+client-roles=Roles de Cliente
+select-client-to-view-roles=Selecciona el cliente para ver sus roles
+available-roles.tooltip=Roles de este cliente que puedes asociar a este rol compuesto.
+client.associated-roles.tooltip=Roles de cliente asociados con este rol compuesto.
+add-builtin=A\u00F1adir Builtin
+category=Categor\u00EDa
+type=Tipo
+no-mappers-available=No hay asignadores disponibles
+add-builtin-protocol-mappers=A\u00F1adir Builtin Protocol Mappers
+add-builtin-protocol-mapper=A\u00F1adir Builtin Protocol Mapper
+scope-mappings=Asignaciones de \u00E1mbito
+full-scope-allowed=Permitir todos los \u00E1mbitos
+full-scope-allowed.tooltip=Permite deshabilitar todas las restricciones.
+scope.available-roles.tooltip=Roles de dominio que pueden ser asignados al \u00E1mbito
+assigned-roles=Roles Asignados
+assigned-roles.tooltip=Roles a nivel de dominio asignados a este \u00E1mbito.
+effective-roles=Roles Efectivos
+realm.effective-roles.tooltip=Roles de dominio asignados que pueden haber sido heredados de un rol compuesto.
+select-client-roles.tooltip=Selecciona el cliente para ver sus roles
+assign.available-roles.tooltip=Roles de clientes disponibles para ser asignados.
+client.assigned-roles.tooltip=Roles de cliente asignados
+client.effective-roles.tooltip=Roles de cliente asignados que pueden haber sido heredados desde un rol compuesto.
+basic-configuration=Configuraci\u00F3n b\u00E1sica
+node-reregistration-timeout=Tiempo de espera de re-registro de nodo
+node-reregistration-timeout.tooltip=Indica el m\u00E1ximo intervalo de tiempo para que los nodos del cluster registrados se vuelvan a registrar. Si el nodo del cluster no env\u00EDa una petici\u00F3n de re-registro a Keycloak dentro de este intervalo, ser\u00E1 desregistrado de Keycloak
+registered-cluster-nodes=Registrar nodos de cluster
+register-node-manually=Registrar nodo manualmente
+test-cluster-availability=Probar disponibilidad del cluster
+last-registration=\u00DAltimo registro
+node-host=Host del nodo
+no-registered-cluster-nodes=No hay nodos de cluster registrados disponibles
+cluster-nodes=Nodos de cluster
+add-node=A\u00F1adir Nodo
+active-sessions.tooltip=N\u00FAmero total de sesiones activas para este cliente.
+show-sessions=Mostrar sesiones
+show-sessions.tooltip=Advertencia, esta es una operaci\u00F3n potencialmente costosa dependiendo del n\u00FAmero de sesiones activas.
+user=Usuario
+from-ip=Desde IP
+session-start=Inicio de sesi\u00F3n
+first-page=Primera p\u00E1gina
+previous-page=P\u00E1gina Anterior
+next-page=P\u00E1gina siguiente
+client-revoke.not-before.tooltip=Revocar todos los tokens emitidos antes de esta fecha para este cliente.
+client-revoke.push.tooltip=Si la URL de administraci\u00F3n est\u00E1 configurada para este cliente, env\u00EDa esta pol\u00EDtica a este cliente.
+select-a-format=Selecciona un formato
+download=Descargar
+offline-tokens=Tokens sin conexi\u00F3n
+offline-tokens.tooltip=N\u00FAmero total de tokens sin conexi\u00F3n de este cliente.
+show-offline-tokens=Mostrar tokens sin conexi\u00F3n
+show-offline-tokens.tooltip=Advertencia, esta es una operaci\u00F3n potencialmente costosa dependiendo del n\u00FAmero de tokens sin conexi\u00F3n.
+token-issued=Token expedido
+last-access=\u00DAltimo Acceso
+last-refresh=\u00DAltima actualizaci\u00F3n
+key-export=Exportar clave
+key-import=Importar clave
+export-saml-key=Exportar clave SAML
+import-saml-key=Importar clave SAML
+realm-certificate-alias=Alias del certificado del dominio
+realm-certificate-alias.tooltip=El certificado del dominio es almacenado en archivo. Este es el alias al mismo.
+signing-key=Clave de firma
+saml-signing-key=Clave de firma SAML.
+private-key=Clave Privada
+generate-new-keys=Generar nuevas claves
+export=Exportar
+encryption-key=Clave de cifrado
+saml-encryption-key.tooltip=Clave de cifrado de SAML
+service-accounts=Cuentas de servicio
+service-account.available-roles.tooltip=Roles de dominio que pueden ser asignados a la cuenta del servicio.
+service-account.assigned-roles.tooltip=Roles de dominio asignados a la cuenta del servicio.
+service-account-is-not-enabled-for=La cuenta del servicio no est\u00E1 habilitada para {{client}}
+create-protocol-mappers=Crear asignadores de protocolo
+create-protocol-mapper=Crear asignador de protocolo
+protocol=Protocolo
+protocol.tooltip=Protocolo.
+id=ID
+mapper.name.tooltip=Nombre del asignador.
+mapper.consent-required.tooltip=Cuando se concede acceso temporal, \u00BFes necesario el consentimiento del usuario para proporcinar estos datos al cliente cliente?
+consent-text=Texto del consentimiento
+consent-text.tooltip=Texto para mostrar en la p\u00E1gina de consentimiento.
+mapper-type=Tipo de asignador
+
+# realm identity providers
+identity-providers=Proveedores de identidad
+table-of-identity-providers=Tabla de proveedores de identidad
+add-provider.placeholder=A\u00F1adir proveedor...
+provider=Proveedor
+gui-order=Orden en la interfaz gr\u00E1fica (GUI)
+redirect-uri=URI de redirecci\u00F3n
+redirect-uri.tooltip=La URI de redirecci\u00F3n usada para configurar el proveedor de identidad.
+alias=Alias
+identity-provider.alias.tooltip=El alias que identifica de forma \u00FAnica un proveedor de identidad, se usa tambi\u00E9n para construir la URI de redirecci\u00F3n.
+identity-provider.enabled.tooltip=Habilita/deshabilita este proveedor de identidad.
+authenticate-by-default=Autenticar por defecto
+identity-provider.authenticate-by-default.tooltip=Indica si este proveedor deber\u00EDa ser probado por defecto para autenticacaci\u00F3n incluso antes de mostrar la p\u00E1gina de inicio de sesi\u00F3n.
+store-tokens=Almacenar tokens
+identity-provider.store-tokens.tooltip=Hablitar/deshabilitar si los tokens deben ser almacenados despu\u00E9s de autenticar a los usuarios.
+stored-tokens-readable=Tokens almacenados legibles
+identity-provider.stored-tokens-readable.tooltip=Habilitar/deshabilitar is los nuevos usuarios pueden leear los tokens almacenados. Esto asigna el rol 'broker.read-token'.
+update-profile-on-first-login=Actualizar perfil en el primer inicio de sesi\u00F3n
+on=Activado
+on-missing-info=Si falta informaci\u00F3n
+off=Desactivado
+update-profile-on-first-login.tooltip=Define condiciones bajos las cuales un usuario tiene que actualizar su perfil durante el primer inicio de sesi\u00F3n.
+trust-email=Confiar en el email
+trust-email.tooltip=Si est\u00E1 habilitado, el email recibido de este proveedor no se verificar\u00E1 aunque la verificaci\u00F3n est\u00E9 habilitada para el dominio.
+gui-order.tooltip=N\u00FAmero que define el orden del proveedor en la interfaz gr\u00E1fica (GUI) (ej. en la p\u00E1gina de inicio de sesi\u00F3n)
+openid-connect-config=Configuraci\u00F3n de OpenID Connect
+openid-connect-config.tooltip=Configuraci\u00F3n de OIDC SP e IDP externos
+authorization-url=URL de autorizaci\u00F3n
+authorization-url.tooltip=La URL de autorizaci\u00F3n.
+token-url=Token URL
+token-url.tooltip=La URL del token.
+logout-url=URL de desconexi\u00F3n
+identity-provider.logout-url.tooltip=Punto de cierre de sesi\u00F3n para usar en la desconexi\u00F3n de usuarios desde un proveedor de identidad (IDP) externo.
+backchannel-logout=Backchannel Logout
+backchannel-logout.tooltip=Does the external IDP support backchannel logout?
+user-info-url=URL de informaci\u00F3n de usuario
+user-info-url.tooltip=.La URL de informaci\u00F3n de usuario. Opcional
+identity-provider.client-id.tooltip=El cliente o identificador de cliente registrado en el proveedor de identidad.
+client-secret=Secreto de Cliente
+show-secret=Mostrar secreto
+hide-secret=Ocultar secreto
+client-secret.tooltip=El cliente o el secreto de cliente registrado en el proveedor de identidad.
+issuer=Emisor
+issuer.tooltip=El identificador del emisor para el emisor de la respuesta. Si no se indica, no se realizar\u00E1 ninguna validaci\u00F3n.
+default-scopes=\u00C1mbitos por defecto
+identity-provider.default-scopes.tooltip=Los \u00E1mbitos que se enviar\u00E1n cuando se solicite autorizaci\u00F3n. Puede ser una lista de \u00E1mbitos separados por espacios. El valor por defecto es 'openid'.
+prompt=Prompt
+unspecified.option=no especificado
+none.option=ninguno
+consent.option=consentimiento
+login.option=login
+select-account.option=select_account
+prompt.tooltip=Indica si el servidor de autorizaci\u00F3n solicita al usuario final para reautenticaci\u00F3n y consentimiento.
+validate-signatures=Validar firmas
+identity-provider.validate-signatures.tooltip=Habilitar/deshabilitar la validaci\u00F3n de firmas de proveedores de identidad (IDP) externos
+validating-public-key=Validando clave p\u00FAblica
+identity-provider.validating-public-key.tooltip=La clave p\u00FAblica en formato PEM que debe usarse para verificar las firmas de proveedores de identidad (IDP) externos.
+import-external-idp-config=Importar configuraci\u00F3n externa de IDP
+import-external-idp-config.tooltip=Te permite cargar metadatos de un proveedor de identidad (IDP) externo de un archivo de coniguraci\u00F3n o descargarlo desde una URL.
+import-from-url=Importar desde URL
+identity-provider.import-from-url.tooltip=Importar metadatos desde un descriptor de un proveedor de identidad (IDP) remoto.
+import-from-file=Importar desde archivo
+identity-provider.import-from-file.tooltip=Importar metadatos desde un descriptor de un proveedor de identidad (IDP) descargado.
+saml-config=Configuraci\u00F3n SAML
+identity-provider.saml-config.tooltip=Configurci\u00F3n de proveedor SAML e IDP externo
+single-signon-service-url=URL de servicio de conexi\u00F3n \u00FAnico (SSO)
+saml.single-signon-service-url.tooltip=La URL que debe ser usada para enviar peticiones de autenticaci\u00F3n (SAML AuthnRequest).
+single-logout-service-url=URL de servicio de desconexi\u00F3n \u00FAnico
+saml.single-logout-service-url.tooltip=La URL que debe usarse para enviar peticiones de desconexi\u00F3n.
+nameid-policy-format=Formato de pol\u00EDtica NameID
+nameid-policy-format.tooltip=Indica la referencia a la URI correspondiente a un formato de NameID. El valor por defecto es urn:oasis:names:tc:SAML:2.0:nameid-format:persistent.
+http-post-binding-response=HTTP-POST enlace de respuesta
+http-post-binding-response.tooltip=Indica si se reponde a las peticiones usando HTTP-POST. Si no est\u00E1 activado, se usa HTTP-REDIRECT. 
+http-post-binding-for-authn-request=HTTP-POST para AuthnRequest
+http-post-binding-for-authn-request.tooltip=Indica si AuthnRequest debe ser envianda usando HTTP-POST. Si no est\u00E1 activado se hace HTTP-REDIRECT.
+want-authn-requests-signed=Firmar AuthnRequests
+want-authn-requests-signed.tooltip=Indica si el proveedor de identidad espera recibir firmadas las AuthnRequest.
+force-authentication=Forzar autenticaci\u00F3n
+identity-provider.force-authentication.tooltip=Indica si el proveedor de identidad debe autenticar al presentar directamente las credenciales en lugar de depender de un contexto de seguridad previo.
+validate-signature=Validar firma
+saml.validate-signature.tooltip=Habilitar/deshabilitar la validaci\u00F3n de firma en respuestas SAML.
+validating-x509-certificate=Validando certificado X509
+validating-x509-certificate.tooltip=El certificado en formato PEM que debe usarse para comprobar las firmas.
+saml.import-from-url.tooltip=Importar metadatos desde un descriptor de entidad remoto de un IDP de SAML
+social.client-id.tooltip=El identificador del cliente registrado con el proveedor de identidad.
+social.client-secret.tooltip=El secreto del cliente registrado con el proveedor de identidad.
+social.default-scopes.tooltip=\u00C1mbitos que se enviar\u00E1n cuando se solicite autorizaci\u00F3n. Ver la documentaci\u00F3n para los posibles valores, separador y valor por defecto.
+key=Clave
+stackoverflow.key.tooltip=La clave obtenida en el registro del cliente de Stack Overflow.
+
+realms=Dominios
+realm=Dominio
+
+identity-provider-mappers=Asignadores de proveedores de identidad (IDP)
+create-identity-provider-mapper=Crear asignador de proveedor de identidad (IDP)
+add-identity-provider-mapper=A\u00F1adir asignador de proveedor de identidad
+client.description.tooltip=Indica la descripci\u00F3n del cliente. Por ejemplo 'My Client for TimeSheets'. Tambi\u00E9n soporta claves para valores localizzados. Por ejemplo: ${my_client_description}
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/messages/messages_es.properties b/forms/common-themes/src/main/resources/theme/base/admin/messages/messages_es.properties
new file mode 100644
index 0000000..b5b7ca8
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/messages/messages_es.properties
@@ -0,0 +1,8 @@
+invalidPasswordMinLengthMessage=Contrase\u00F1a incorrecta: longitud m\u00EDnima {0}.
+invalidPasswordMinLowerCaseCharsMessage=Contrase\u00F1a incorrecta: debe contener al menos {0} letras min\u00FAsculas.
+invalidPasswordMinDigitsMessage=Contrase\u00F1a incorrecta: debe contaner al menos {0} caracteres num\u00E9ricos.
+invalidPasswordMinUpperCaseCharsMessage=Contrase\u00F1a incorrecta: debe contener al menos {0} letras may\u00FAsculas.
+invalidPasswordMinSpecialCharsMessage=Contrase\u00F1a incorrecta: debe contener al menos {0} caracteres especiales.
+invalidPasswordNotUsernameMessage=Contrase\u00F1a incorrecta: no puede ser igual al nombre de usuario.
+invalidPasswordRegexPatternMessage=Contrase\u00F1a incorrecta: no cumple la expresi\u00F3n regular.
+invalidPasswordHistoryMessage=Contrase\u00F1a incorrecta: no puede ser igual a ninguna de las \u00FAltimas {0} contrase\u00F1as.
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
index f1d922b..bfbcee9 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
@@ -7,7 +7,7 @@ var configUrl = consoleBaseUrl + "/config";
 
 var auth = {};
 
-var module = angular.module('keycloak', [ 'keycloak.services', 'keycloak.loaders', 'ui.bootstrap', 'ui.select2', 'angularFileUpload', 'pascalprecht.translate', 'ngCookies', 'ngSanitize']);
+var module = angular.module('keycloak', [ 'keycloak.services', 'keycloak.loaders', 'ui.bootstrap', 'ui.select2', 'angularFileUpload', 'angularTreeview', 'pascalprecht.translate', 'ngCookies', 'ngSanitize']);
 var resourceRequests = 0;
 var loadingTimer = -1;
 
@@ -176,6 +176,27 @@ module.config([ '$routeProvider', function($routeProvider) {
             },
             controller : 'RealmTokenDetailCtrl'
         })
+        .when('/realms/:realm/client-initial-access', {
+            templateUrl : resourceUrl + '/partials/client-initial-access.html',
+            resolve : {
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                },
+                clientInitialAccess : function(ClientInitialAccessLoader) {
+                    return ClientInitialAccessLoader();
+                }
+            },
+            controller : 'ClientInitialAccessCtrl'
+        })
+        .when('/realms/:realm/client-initial-access/create', {
+            templateUrl : resourceUrl + '/partials/client-initial-access-create.html',
+            resolve : {
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                }
+            },
+            controller : 'ClientInitialAccessCreateCtrl'
+        })
         .when('/realms/:realm/keys-settings', {
             templateUrl : resourceUrl + '/partials/realm-keys.html',
             resolve : {
@@ -199,6 +220,9 @@ module.config([ '$routeProvider', function($routeProvider) {
                 },
                 providerFactory : function(IdentityProviderFactoryLoader) {
                     return {};
+                },
+                authFlows : function(AuthenticationFlowsLoader) {
+                    return {};
                 }
             },
             controller : 'RealmIdentityProviderCtrl'
@@ -217,6 +241,9 @@ module.config([ '$routeProvider', function($routeProvider) {
                 },
                 providerFactory : function(IdentityProviderFactoryLoader) {
                     return new IdentityProviderFactoryLoader();
+                },
+                authFlows : function(AuthenticationFlowsLoader) {
+                    return AuthenticationFlowsLoader();
                 }
             },
             controller : 'RealmIdentityProviderCtrl'
@@ -235,6 +262,9 @@ module.config([ '$routeProvider', function($routeProvider) {
                 },
                 providerFactory : function(IdentityProviderFactoryLoader) {
                     return IdentityProviderFactoryLoader();
+                },
+                authFlows : function(AuthenticationFlowsLoader) {
+                    return AuthenticationFlowsLoader();
                 }
             },
             controller : 'RealmIdentityProviderCtrl'
@@ -438,6 +468,21 @@ module.config([ '$routeProvider', function($routeProvider) {
             },
             controller : 'UserRoleMappingCtrl'
         })
+        .when('/realms/:realm/users/:user/groups', {
+            templateUrl : resourceUrl + '/partials/user-group-membership.html',
+            resolve : {
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                },
+                user : function(UserLoader) {
+                    return UserLoader();
+                },
+                groups : function(GroupListLoader) {
+                    return GroupListLoader();
+                }
+            },
+            controller : 'UserGroupMembershipCtrl'
+        })
         .when('/realms/:realm/users/:user/sessions', {
             templateUrl : resourceUrl + '/partials/user-sessions.html',
             resolve : {
@@ -574,6 +619,97 @@ module.config([ '$routeProvider', function($routeProvider) {
             },
             controller : 'RoleListCtrl'
         })
+        .when('/realms/:realm/groups', {
+            templateUrl : resourceUrl + '/partials/group-list.html',
+            resolve : {
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                },
+                groups : function(GroupListLoader) {
+                    return GroupListLoader();
+                }
+            },
+            controller : 'GroupListCtrl'
+        })
+        .when('/create/group/:realm/parent/:parentId', {
+            templateUrl : resourceUrl + '/partials/create-group.html',
+            resolve : {
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                },
+                parentId : function($route) {
+                    return $route.current.params.parentId;
+                }
+            },
+            controller : 'GroupCreateCtrl'
+        })
+        .when('/realms/:realm/groups/:group', {
+            templateUrl : resourceUrl + '/partials/group-detail.html',
+            resolve : {
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                },
+                group : function(GroupLoader) {
+                    return GroupLoader();
+                }
+            },
+            controller : 'GroupDetailCtrl'
+        })
+        .when('/realms/:realm/groups/:group/attributes', {
+            templateUrl : resourceUrl + '/partials/group-attributes.html',
+            resolve : {
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                },
+                group : function(GroupLoader) {
+                    return GroupLoader();
+                }
+            },
+            controller : 'GroupDetailCtrl'
+        })
+        .when('/realms/:realm/groups/:group/members', {
+            templateUrl : resourceUrl + '/partials/group-members.html',
+            resolve : {
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                },
+                group : function(GroupLoader) {
+                    return GroupLoader();
+                }
+            },
+            controller : 'GroupMembersCtrl'
+        })
+        .when('/realms/:realm/groups/:group/role-mappings', {
+            templateUrl : resourceUrl + '/partials/group-role-mappings.html',
+            resolve : {
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                },
+                group : function(GroupLoader) {
+                    return GroupLoader();
+                },
+                clients : function(ClientListLoader) {
+                    return ClientListLoader();
+                },
+                client : function() {
+                    return {};
+                }
+            },
+            controller : 'GroupRoleMappingCtrl'
+        })
+        .when('/realms/:realm/default-groups', {
+            templateUrl : resourceUrl + '/partials/default-groups.html',
+            resolve : {
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                },
+                groups : function(GroupListLoader) {
+                    return GroupListLoader();
+                }
+            },
+            controller : 'DefaultGroupsCtrl'
+        })
+
 
         .when('/create/role/:realm/clients/:client', {
             templateUrl : resourceUrl + '/partials/client-role-detail.html',
@@ -1339,12 +1475,15 @@ module.config([ '$routeProvider', function($routeProvider) {
             },
             controller : 'RealmOtpPolicyCtrl'
         })
-        .when('/realms/:realm/authentication/config/:provider/:config', {
+        .when('/realms/:realm/authentication/flows/:flow/config/:provider/:config', {
             templateUrl : resourceUrl + '/partials/authenticator-config.html',
             resolve : {
                 realm : function(RealmLoader) {
                     return RealmLoader();
                 },
+                flow : function(AuthenticationFlowLoader) {
+                    return AuthenticationFlowLoader();
+                },
                 configType : function(AuthenticationConfigDescriptionLoader) {
                     return AuthenticationConfigDescriptionLoader();
                 },
@@ -1354,12 +1493,15 @@ module.config([ '$routeProvider', function($routeProvider) {
             },
             controller : 'AuthenticationConfigCtrl'
         })
-        .when('/create/authentication/:realm/execution/:executionId/provider/:provider', {
+        .when('/create/authentication/:realm/flows/:flow/execution/:executionId/provider/:provider', {
             templateUrl : resourceUrl + '/partials/authenticator-config.html',
             resolve : {
                 realm : function(RealmLoader) {
                     return RealmLoader();
                 },
+                flow : function(AuthenticationFlowLoader) {
+                    return AuthenticationFlowLoader();
+                },
                 configType : function(AuthenticationConfigDescriptionLoader) {
                     return AuthenticationConfigDescriptionLoader();
                 },
@@ -1856,6 +1998,24 @@ module.directive('kcTabsUser', function () {
     }
 });
 
+module.directive('kcTabsGroup', function () {
+    return {
+        scope: true,
+        restrict: 'E',
+        replace: true,
+        templateUrl: resourceUrl + '/templates/kc-tabs-group.html'
+    }
+});
+
+module.directive('kcTabsGroupList', function () {
+    return {
+        scope: true,
+        restrict: 'E',
+        replace: true,
+        templateUrl: resourceUrl + '/templates/kc-tabs-group-list.html'
+    }
+});
+
 module.directive('kcTabsClient', function () {
     return {
         scope: true,
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
index e35e1c6..971b0c4 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
@@ -30,7 +30,7 @@ module.controller('ClientRoleListCtrl', function($scope, $location, realm, clien
     });
 });
 
-module.controller('ClientCredentialsCtrl', function($scope, $location, realm, client, clientAuthenticatorProviders, clientConfigProperties, Client) {
+module.controller('ClientCredentialsCtrl', function($scope, $location, realm, client, clientAuthenticatorProviders, clientConfigProperties, Client, ClientRegistrationAccessToken, Notifications) {
     $scope.realm = realm;
     $scope.client = angular.copy(client);
     $scope.clientAuthenticatorProviders = clientAuthenticatorProviders;
@@ -68,6 +68,17 @@ module.controller('ClientCredentialsCtrl', function($scope, $location, realm, cl
         }
     }, true);
 
+    $scope.regenerateRegistrationAccessToken = function() {
+        var secret = ClientRegistrationAccessToken.update({ realm : $scope.realm.realm, client : $scope.client.id },
+            function(data) {
+                Notifications.success('The registration access token has been updated.');
+                $scope.client['registrationAccessToken'] = data.registrationAccessToken;
+            },
+            function() {
+                Notifications.error('Failed to update the registration access token');
+            }
+        );
+    };
 });
 
 module.controller('ClientSecretCtrl', function($scope, $location, ClientSecret, Notifications) {
@@ -877,7 +888,7 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, $route, se
     $scope.viewImportDetails = function() {
         $modal.open({
             templateUrl: resourceUrl + '/partials/modal/view-object.html',
-            controller: 'JsonModalCtrl',
+            controller: 'ObjectModalCtrl',
             resolve: {
                 object: function () {
                     return $scope.client;
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/groups.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/groups.js
new file mode 100755
index 0000000..c336dc8
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/groups.js
@@ -0,0 +1,428 @@
+module.controller('GroupListCtrl', function($scope, $route, realm, groups, Groups, Group, GroupChildren, Notifications, $location, Dialog) {
+    $scope.realm = realm;
+    $scope.groupList = [
+        {"id" : "realm", "name": "Groups",
+            "subGroups" : groups}
+    ];
+
+    $scope.tree = [];
+
+    $scope.edit = function(selected) {
+        $location.url("/realms/" + realm.realm + "/groups/" + selected.id);
+    }
+
+    $scope.cut = function(selected) {
+        $scope.cutNode = selected;
+    }
+
+    $scope.isDisabled = function() {
+        if (!$scope.tree.currentNode) return true;
+        return $scope.tree.currentNode.id == 'realm';
+    }
+
+    $scope.paste = function(selected) {
+        if (selected == null) return;
+        if ($scope.cutNode == null) return;
+        if (selected.id == 'realm') {
+            Groups.save({realm: realm.realm}, {id:$scope.cutNode.id}, function() {
+                $route.reload();
+                Notifications.success("Group moved.");
+
+            });
+
+        } else {
+            GroupChildren.save({realm: realm.realm, groupId: selected.id}, {id:$scope.cutNode.id}, function() {
+                $route.reload();
+                Notifications.success("Group moved.");
+
+            });
+
+        }
+
+    }
+
+    $scope.remove = function(selected) {
+        if (selected == null) return;
+        Dialog.confirmDelete(selected.name, 'group', function() {
+            Group.remove({ realm: realm.realm, groupId : selected.id }, function() {
+                $route.reload();
+                Notifications.success("The group has been deleted.");
+            });
+        });
+
+    }
+
+    $scope.createGroup = function(selected) {
+        var parent = 'realm';
+        if (selected) {
+            parent = selected.id;
+        }
+        $location.url("/create/group/" + realm.realm + '/parent/' + parent);
+
+    }
+    var isLeaf = function(node) {
+        return node.id != "realm" && (!node.subGroups || node.subGroups.length == 0);
+    }
+
+    $scope.getGroupClass = function(node) {
+        if (node.id == "realm") {
+            return 'pficon pficon-users';
+        }
+        if (isLeaf(node)) {
+            return 'normal';
+        }
+        if (node.subGroups.length && node.collapsed) return 'collapsed';
+        if (node.subGroups.length && !node.collapsed) return 'expanded';
+        return 'collapsed';
+
+    }
+
+    $scope.getSelectedClass = function(node) {
+        if (node.selected) {
+            return 'selected';
+        } else if ($scope.cutNode && $scope.cutNode.id == node.id) {
+            return 'cut';
+        }
+        return undefined;
+    }
+
+});
+
+module.controller('GroupCreateCtrl', function($scope, $route, realm, parentId, Groups, Group, GroupChildren, Notifications, $location) {
+    $scope.realm = realm;
+    $scope.group = {};
+    $scope.save = function() {
+        console.log('save!!!');
+        if (parentId == 'realm') {
+            console.log('realm')
+            Groups.save({realm: realm.realm}, $scope.group, function(data, headers) {
+                var l = headers().location;
+
+
+                var id = l.substring(l.lastIndexOf("/") + 1);
+
+                $location.url("/realms/" + realm.realm + "/groups/" + id);
+                Notifications.success("Group Created.");
+            })
+
+        } else {
+            GroupChildren.save({realm: realm.realm, groupId: parentId}, $scope.group, function(data, headers) {
+                var l = headers().location;
+
+
+                var id = l.substring(l.lastIndexOf("/") + 1);
+
+                $location.url("/realms/" + realm.realm + "/groups/" + id);
+                Notifications.success("Group Created.");
+            })
+
+        }
+
+    }
+    $scope.cancel = function() {
+        $location.url("/realms/" + realm.realm + "/groups");
+    };
+});
+
+module.controller('GroupTabCtrl', function(Dialog, $scope, Current, Group, Notifications, $location) {
+    $scope.removeGroup = function() {
+        Dialog.confirmDelete($scope.group.name, 'group', function() {
+            Group.remove({
+                realm : Current.realm.realm,
+                groupId : $scope.group.id
+            }, function() {
+                $location.url("/realms/" + Current.realm.realm + "/groups");
+                Notifications.success("The group has been deleted.");
+            });
+        });
+    };
+});
+
+module.controller('GroupDetailCtrl', function(Dialog, $scope, realm, group, Group, Notifications, $location) {
+    $scope.realm = realm;
+
+    if (!group.attributes) {
+        group.attributes = {}
+    }
+    convertAttributeValuesToString(group);
+
+
+    $scope.group = angular.copy(group);
+
+    $scope.changed = false; // $scope.create;
+    $scope.$watch('group', function() {
+        if (!angular.equals($scope.group, group)) {
+            $scope.changed = true;
+        }
+    }, true);
+
+    $scope.save = function() {
+        convertAttributeValuesToLists();
+
+        Group.update({
+            realm: realm.realm,
+            groupId: $scope.group.id
+        }, $scope.group, function () {
+            $scope.changed = false;
+            convertAttributeValuesToString($scope.group);
+            group = angular.copy($scope.group);
+            Notifications.success("Your changes have been saved to the group.");
+        });
+    };
+
+    function convertAttributeValuesToLists() {
+        var attrs = $scope.group.attributes;
+        for (var attribute in attrs) {
+            if (typeof attrs[attribute] === "string") {
+                var attrVals = attrs[attribute].split("##");
+                attrs[attribute] = attrVals;
+            }
+        }
+    }
+
+    function convertAttributeValuesToString(group) {
+        var attrs = group.attributes;
+        for (var attribute in attrs) {
+            if (typeof attrs[attribute] === "object") {
+                var attrVals = attrs[attribute].join("##");
+                attrs[attribute] = attrVals;
+            }
+        }
+    }
+
+    $scope.reset = function() {
+        $scope.group = angular.copy(group);
+        $scope.changed = false;
+    };
+
+    $scope.cancel = function() {
+        $location.url("/realms/" + realm.realm + "/groups");
+    };
+
+    $scope.addAttribute = function() {
+        $scope.group.attributes[$scope.newAttribute.key] = $scope.newAttribute.value;
+        delete $scope.newAttribute;
+    }
+
+    $scope.removeAttribute = function(key) {
+        delete $scope.group.attributes[key];
+    }
+});
+
+module.controller('GroupRoleMappingCtrl', function($scope, $http, realm, group, clients, client, Notifications, GroupRealmRoleMapping,
+                                                  GroupClientRoleMapping, GroupAvailableRealmRoleMapping, GroupAvailableClientRoleMapping,
+                                                  GroupCompositeRealmRoleMapping, GroupCompositeClientRoleMapping) {
+    $scope.realm = realm;
+    $scope.group = group;
+    $scope.selectedRealmRoles = [];
+    $scope.selectedRealmMappings = [];
+    $scope.realmMappings = [];
+    $scope.clients = clients;
+    $scope.client = client;
+    $scope.clientRoles = [];
+    $scope.clientComposite = [];
+    $scope.selectedClientRoles = [];
+    $scope.selectedClientMappings = [];
+    $scope.clientMappings = [];
+    $scope.dummymodel = [];
+
+    $scope.realmMappings = GroupRealmRoleMapping.query({realm : realm.realm, groupId : group.id});
+    $scope.realmRoles = GroupAvailableRealmRoleMapping.query({realm : realm.realm, groupId : group.id});
+    $scope.realmComposite = GroupCompositeRealmRoleMapping.query({realm : realm.realm, groupId : group.id});
+
+    $scope.addRealmRole = function() {
+        var roles = $scope.selectedRealmRoles;
+        $scope.selectedRealmRoles = [];
+        $http.post(authUrl + '/admin/realms/' + realm.realm + '/groups/' + group.id + '/role-mappings/realm',
+            roles).success(function() {
+                $scope.realmMappings = GroupRealmRoleMapping.query({realm : realm.realm, groupId : group.id});
+                $scope.realmRoles = GroupAvailableRealmRoleMapping.query({realm : realm.realm, groupId : group.id});
+                $scope.realmComposite = GroupCompositeRealmRoleMapping.query({realm : realm.realm, groupId : group.id});
+                $scope.selectedRealmMappings = [];
+                $scope.selectRealmRoles = [];
+                if ($scope.targetClient) {
+                    console.log('load available');
+                    $scope.clientComposite = GroupCompositeClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id});
+                    $scope.clientRoles = GroupAvailableClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id});
+                    $scope.clientMappings = GroupClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id});
+                    $scope.selectedClientRoles = [];
+                    $scope.selectedClientMappings = [];
+                }
+                Notifications.success("Role mappings updated.");
+
+            });
+    };
+
+    $scope.deleteRealmRole = function() {
+        $http.delete(authUrl + '/admin/realms/' + realm.realm + '/groups/' + group.id + '/role-mappings/realm',
+            {data : $scope.selectedRealmMappings, headers : {"content-type" : "application/json"}}).success(function() {
+                $scope.realmMappings = GroupRealmRoleMapping.query({realm : realm.realm, groupId : group.id});
+                $scope.realmRoles = GroupAvailableRealmRoleMapping.query({realm : realm.realm, groupId : group.id});
+                $scope.realmComposite = GroupCompositeRealmRoleMapping.query({realm : realm.realm, groupId : group.id});
+                $scope.selectedRealmMappings = [];
+                $scope.selectRealmRoles = [];
+                if ($scope.targetClient) {
+                    console.log('load available');
+                    $scope.clientComposite = GroupCompositeClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id});
+                    $scope.clientRoles = GroupAvailableClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id});
+                    $scope.clientMappings = GroupClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id});
+                    $scope.selectedClientRoles = [];
+                    $scope.selectedClientMappings = [];
+                }
+                Notifications.success("Role mappings updated.");
+            });
+    };
+
+    $scope.addClientRole = function() {
+        $http.post(authUrl + '/admin/realms/' + realm.realm + '/groups/' + group.id + '/role-mappings/clients/' + $scope.targetClient.id,
+            $scope.selectedClientRoles).success(function() {
+                $scope.clientMappings = GroupClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id});
+                $scope.clientRoles = GroupAvailableClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id});
+                $scope.clientComposite = GroupCompositeClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id});
+                $scope.selectedClientRoles = [];
+                $scope.selectedClientMappings = [];
+                $scope.realmComposite = GroupCompositeRealmRoleMapping.query({realm : realm.realm, groupId : group.id});
+                $scope.realmRoles = GroupAvailableRealmRoleMapping.query({realm : realm.realm, groupId : group.id});
+                Notifications.success("Role mappings updated.");
+            });
+    };
+
+    $scope.deleteClientRole = function() {
+        $http.delete(authUrl + '/admin/realms/' + realm.realm + '/groups/' + group.id + '/role-mappings/clients/' + $scope.targetClient.id,
+            {data : $scope.selectedClientMappings, headers : {"content-type" : "application/json"}}).success(function() {
+                $scope.clientMappings = GroupClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id});
+                $scope.clientRoles = GroupAvailableClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id});
+                $scope.clientComposite = GroupCompositeClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id});
+                $scope.selectedClientRoles = [];
+                $scope.selectedClientMappings = [];
+                $scope.realmComposite = GroupCompositeRealmRoleMapping.query({realm : realm.realm, groupId : group.id});
+                $scope.realmRoles = GroupAvailableRealmRoleMapping.query({realm : realm.realm, groupId : group.id});
+                Notifications.success("Role mappings updated.");
+            });
+    };
+
+
+    $scope.changeClient = function() {
+        if ($scope.targetClient) {
+            $scope.clientComposite = GroupCompositeClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id});
+            $scope.clientRoles = GroupAvailableClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id});
+            $scope.clientMappings = GroupClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id});
+        } else {
+            $scope.clientRoles = null;
+            $scope.clientMappings = null;
+            $scope.clientComposite = null;
+        }
+        $scope.selectedClientRoles = [];
+        $scope.selectedClientMappings = [];
+    };
+
+
+
+});
+
+module.controller('GroupMembersCtrl', function($scope, realm, group, GroupMembership) {
+    $scope.realm = realm;
+    $scope.page = 0;
+    $scope.group = group;
+
+    $scope.query = {
+        realm: realm.realm,
+        groupId: group.id,
+        max : 5,
+        first : 0
+    }
+
+
+    $scope.firstPage = function() {
+        $scope.query.first = 0;
+        $scope.searchQuery();
+    }
+
+    $scope.previousPage = function() {
+        $scope.query.first -= parseInt($scope.query.max);
+        if ($scope.query.first < 0) {
+            $scope.query.first = 0;
+        }
+        $scope.searchQuery();
+    }
+
+    $scope.nextPage = function() {
+        $scope.query.first += parseInt($scope.query.max);
+        $scope.searchQuery();
+    }
+
+    $scope.searchQuery = function() {
+        console.log("query.search: " + $scope.query.search);
+        $scope.searchLoaded = false;
+
+        $scope.users = GroupMembership.query($scope.query, function() {
+            console.log('search loaded');
+            $scope.searchLoaded = true;
+            $scope.lastSearch = $scope.query.search;
+        });
+    };
+
+    $scope.searchQuery();
+
+});
+
+module.controller('DefaultGroupsCtrl', function($scope, $route, realm, groups, DefaultGroups, Notifications, $location, Dialog) {
+    $scope.realm = realm;
+    $scope.groupList = groups;
+    $scope.selectedGroup = null;
+    $scope.tree = [];
+
+    DefaultGroups.query({realm: realm.realm}, function(data) {
+        $scope.defaultGroups = data;
+
+    });
+
+    $scope.addDefaultGroup = function() {
+        if (!$scope.tree.currentNode) {
+            Notifications.error('Please select a group to add');
+            return;
+        };
+
+        DefaultGroups.update({realm: realm.realm, groupId: $scope.tree.currentNode.id}, function() {
+            Notifications.success('Added default group');
+            $route.reload();
+        });
+
+    };
+
+    $scope.removeDefaultGroup = function() {
+        DefaultGroups.remove({realm: realm.realm, groupId: $scope.selectedGroup.id}, function() {
+            Notifications.success('Removed default group');
+            $route.reload();
+        });
+
+    };
+
+    var isLeaf = function(node) {
+        return node.id != "realm" && (!node.subGroups || node.subGroups.length == 0);
+    };
+
+    $scope.getGroupClass = function(node) {
+        if (node.id == "realm") {
+            return 'pficon pficon-users';
+        }
+        if (isLeaf(node)) {
+            return 'normal';
+        }
+        if (node.subGroups.length && node.collapsed) return 'collapsed';
+        if (node.subGroups.length && !node.collapsed) return 'expanded';
+        return 'collapsed';
+
+    }
+
+    $scope.getSelectedClass = function(node) {
+        if (node.selected) {
+            return 'selected';
+        } else if ($scope.cutNode && $scope.cutNode.id == node.id) {
+            return 'cut';
+        }
+        return undefined;
+    }
+
+});
+
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
index 4a4fe89..a837f4f 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
@@ -337,7 +337,7 @@ module.controller('RealmThemeCtrl', function($scope, Current, Realm, realm, serv
     $scope.supportedLocalesOptions = {
         'multiple' : true,
         'simple_tags' : true,
-        'tags' : ['en', 'de', 'pt-BR', 'it']
+        'tags' : ['en', 'de', 'pt-BR', 'it', 'es']
     };
 
     $scope.$watch('realm.supportedLocales', function(oldVal, newVal) {
@@ -594,20 +594,11 @@ module.controller('IdentityProviderTabCtrl', function(Dialog, $scope, Current, N
     };
 });
 
-module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload, $http, $route, realm, instance, providerFactory, IdentityProvider, serverInfo, $location, Notifications, Dialog) {
+module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload, $http, $route, realm, instance, providerFactory, IdentityProvider, serverInfo, authFlows, $location, Notifications, Dialog) {
     console.log('RealmIdentityProviderCtrl');
 
     $scope.realm = angular.copy(realm);
 
-    $scope.initProvider = function() {
-        if (instance && instance.alias) {
-
-        } else {
-            $scope.identityProvider.updateProfileFirstLoginMode = "on";
-        }
-
-    };
-
     $scope.initSamlProvider = function() {
         $scope.nameIdFormats = [
             /*
@@ -658,7 +649,6 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload
         } else {
             $scope.identityProvider.config.nameIDPolicyFormat = $scope.nameIdFormats[0].format;
             $scope.identityProvider.config.signatureAlgorithm = $scope.signatureAlgorithms[1];
-            $scope.identityProvider.updateProfileFirstLoginMode = "off";
         }
     }
 
@@ -676,8 +666,8 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload
         $scope.identityProvider.alias = providerFactory.id;
         $scope.identityProvider.providerId = providerFactory.id;
         $scope.identityProvider.enabled = true;
-        $scope.identityProvider.updateProfileFirstLoginMode = "off";
         $scope.identityProvider.authenticateByDefault = false;
+        $scope.identityProvider.firstBrokerLoginFlowAlias = 'first broker login';
         $scope.newIdentityProvider = true;
     }
 
@@ -696,6 +686,13 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload
 
     $scope.configuredProviders = angular.copy(realm.identityProviders);
 
+    $scope.authFlows = [];
+    for (var i=0 ; i<authFlows.length ; i++) {
+        if (authFlows[i].providerId == 'basic-flow') {
+            $scope.authFlows.push(authFlows[i]);
+        }
+    }
+
     $scope.$watch(function() {
         return $location.path();
     }, function() {
@@ -1901,8 +1898,9 @@ module.controller('RequiredActionsCtrl', function($scope, realm, unregisteredReq
 
 });
 
-module.controller('AuthenticationConfigCtrl', function($scope, realm, configType, config, AuthenticationConfig, Notifications, Dialog, $location) {
+module.controller('AuthenticationConfigCtrl', function($scope, realm, flow, configType, config, AuthenticationConfig, Notifications, Dialog, $location) {
     $scope.realm = realm;
+    $scope.flow = flow;
     $scope.configType = configType;
     $scope.create = false;
     $scope.config = angular.copy(config);
@@ -1927,7 +1925,7 @@ module.controller('AuthenticationConfigCtrl', function($scope, realm, configType
         }, $scope.config, function() {
             $scope.changed = false;
             config = angular.copy($scope.config);
-            $location.url("/realms/" + realm.realm + '/authentication/config/' + configType.providerId + "/" + config.id);
+            $location.url("/realms/" + realm.realm + '/authentication/flows/' + flow.id + '/config/' + configType.providerId + "/" + config.id);
             Notifications.success("Your changes have been saved.");
         });
     };
@@ -1946,15 +1944,16 @@ module.controller('AuthenticationConfigCtrl', function($scope, realm, configType
         Dialog.confirmDelete($scope.config.alias, 'config', function() {
             AuthenticationConfig.remove({ realm: realm.realm, config : $scope.config.id }, function() {
                 Notifications.success("The config has been deleted.");
-                $location.url("/realms/" + realm.realm + '/authentication/flows');
+                $location.url("/realms/" + realm.realm + '/authentication/flows/' + flow.id);
             });
         });
     };
 
 });
 
-module.controller('AuthenticationConfigCreateCtrl', function($scope, realm, configType, execution, AuthenticationExecutionConfig, Notifications, Dialog, $location) {
+module.controller('AuthenticationConfigCreateCtrl', function($scope, realm, flow, configType, execution, AuthenticationExecutionConfig, Notifications, Dialog, $location) {
     $scope.realm = realm;
+    $scope.flow = flow;
     $scope.create = true;
     $scope.config = { config: {}};
     $scope.configType = configType;
@@ -1972,7 +1971,7 @@ module.controller('AuthenticationConfigCreateCtrl', function($scope, realm, conf
         }, $scope.config, function(data, headers) {
             var l = headers().location;
             var id = l.substring(l.lastIndexOf("/") + 1);
-            var url = "/realms/" + realm.realm + '/authentication/config/' + configType.providerId + "/" + id;
+            var url = "/realms/" + realm.realm + '/authentication/flows/' + flow.id + '/config/' + configType.providerId + "/" + id;
             console.log('redirect url: ' + url);
             $location.url(url);
             Notifications.success("Config has been created.");
@@ -1987,6 +1986,69 @@ module.controller('AuthenticationConfigCreateCtrl', function($scope, realm, conf
 
 });
 
+module.controller('ClientInitialAccessCtrl', function($scope, realm, clientInitialAccess, ClientInitialAccess, Dialog, Notifications, $route) {
+    $scope.realm = realm;
+    $scope.clientInitialAccess = clientInitialAccess;
+
+    $scope.remove = function(id) {
+        Dialog.confirmDelete(id, 'initial access token', function() {
+            ClientInitialAccess.remove({ realm: realm.realm, id: id }, function() {
+                Notifications.success("The initial access token was deleted.");
+                $route.reload();
+            });
+        });
+    }
+});
+
+module.controller('ClientInitialAccessCreateCtrl', function($scope, realm, ClientInitialAccess, TimeUnit, Dialog, $location, $translate) {
+    $scope.expirationUnit = 'Days';
+    $scope.expiration = TimeUnit.toUnit(0, $scope.expirationUnit);
+    $scope.count = 1;
+    $scope.realm = realm;
+
+    $scope.$watch('expirationUnit', function(to, from) {
+        $scope.expiration = TimeUnit.convert($scope.expiration, from, to);
+    });
+
+    $scope.save = function() {
+        var expiration = TimeUnit.toSeconds($scope.expiration, $scope.expirationUnit);
+        ClientInitialAccess.save({
+            realm: realm.realm
+        }, { expiration: expiration, count: $scope.count}, function (data) {
+            console.debug(data);
+            $scope.id = data.id;
+            $scope.token = data.token;
+        });
+    };
+
+    $scope.cancel = function() {
+        $location.url('/realms/' + realm.realm + '/client-initial-access');
+    };
+
+    $scope.done = function() {
+        var btns = {
+            ok: {
+                label: $translate.instant('continue'),
+                cssClass: 'btn btn-primary'
+            },
+            cancel: {
+                label: $translate.instant('cancel'),
+                cssClass: 'btn btn-default'
+            }
+        }
+
+        var title = $translate.instant('initial-access-token.confirm.title');
+        var message = $translate.instant('initial-access-token.confirm.text');
+        Dialog.open(title, message, btns, function() {
+            $location.url('/realms/' + realm.realm + '/client-initial-access');
+        });
+    };
+});
+
+
+
+
+
 
 
 
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
index 4f45d63..f42f8aa 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
@@ -1090,3 +1090,64 @@ module.controller('UserFederationMapperCreateCtrl', function($scope, realm, prov
 
 });
 
+module.controller('UserGroupMembershipCtrl', function($scope, $route, realm, groups, user, UserGroupMembership, UserGroupMapping, Notifications, $location, Dialog) {
+    $scope.realm = realm;
+    $scope.user = user;
+    $scope.groupList = groups;
+    $scope.selectedGroup = null;
+    $scope.tree = [];
+
+    UserGroupMembership.query({realm: realm.realm, userId: user.id}, function(data) {
+        $scope.groupMemberships = data;
+
+    });
+
+    $scope.joinGroup = function() {
+        if (!$scope.tree.currentNode) {
+            Notifications.error('Please select a group to add');
+            return;
+        };
+        UserGroupMapping.update({realm: realm.realm, userId: user.id, groupId: $scope.tree.currentNode.id}, function() {
+            Notifications.success('Added group membership');
+            $route.reload();
+        });
+
+    };
+
+    $scope.leaveGroup = function() {
+        UserGroupMapping.remove({realm: realm.realm, userId: user.id, groupId: $scope.selectedGroup.id}, function() {
+            Notifications.success('Removed group membership');
+            $route.reload();
+        });
+
+    };
+
+    var isLeaf = function(node) {
+        return node.id != "realm" && (!node.subGroups || node.subGroups.length == 0);
+    };
+
+    $scope.getGroupClass = function(node) {
+        if (node.id == "realm") {
+            return 'pficon pficon-users';
+        }
+        if (isLeaf(node)) {
+            return 'normal';
+        }
+        if (node.subGroups.length && node.collapsed) return 'collapsed';
+        if (node.subGroups.length && !node.collapsed) return 'expanded';
+        return 'collapsed';
+
+    }
+
+    $scope.getSelectedClass = function(node) {
+        if (node.selected) {
+            return 'selected';
+        } else if ($scope.cutNode && $scope.cutNode.id == node.id) {
+            return 'cut';
+        }
+        return undefined;
+    }
+
+});
+
+
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js
index 7706f0f..61b608f 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js
@@ -458,6 +458,31 @@ module.factory('AuthenticationConfigLoader', function(Loader, AuthenticationConf
     });
 });
 
+module.factory('GroupListLoader', function(Loader, Groups, $route, $q) {
+    return Loader.query(Groups, function() {
+        return {
+            realm : $route.current.params.realm
+        }
+    });
+});
+
+module.factory('GroupLoader', function(Loader, Group, $route, $q) {
+    return Loader.get(Group, function() {
+        return {
+            realm : $route.current.params.realm,
+            groupId : $route.current.params.group
+        }
+    });
+});
+
+module.factory('ClientInitialAccessLoader', function(Loader, ClientInitialAccess, $route) {
+    return Loader.query(ClientInitialAccess, function() {
+        return {
+            realm: $route.current.params.realm
+        }
+    });
+});
+
 
 
 
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
index ec0d475..3ca2183 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
@@ -102,6 +102,10 @@ module.service('Dialog', function($modal) {
         openDialog(title, message, btns, '/templates/kc-modal-message.html').then(success, cancel);
     }
 
+    dialog.open = function(title, message, btns, success, cancel) {
+        openDialog(title, message, btns, '/templates/kc-modal.html').then(success, cancel);
+    }
+
     return dialog
 });
 
@@ -284,6 +288,13 @@ module.service('ServerInfo', function($resource, $q, $http) {
     }
 });
 
+module.factory('ClientInitialAccess', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/clients-initial-access/:id', {
+        realm : '@realm',
+        id : '@id'
+    });
+});
+
 
 module.factory('ClientProtocolMapper', function($resource) {
     return $resource(authUrl + '/admin/realms/:realm/clients/:client/protocol-mappers/models/:id', {
@@ -981,6 +992,17 @@ module.factory('ClientSecret', function($resource) {
     });
 });
 
+module.factory('ClientRegistrationAccessToken', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/clients/:client/registration-access-token', {
+        realm : '@realm',
+        client : '@client'
+    },  {
+        update : {
+            method : 'POST'
+        }
+    });
+});
+
 module.factory('ClientOrigins', function($resource) {
     return $resource(authUrl + '/admin/realms/:realm/clients/:client/allowed-origins', {
         realm : '@realm',
@@ -1442,6 +1464,110 @@ module.service('SelectRoleDialog', function($modal) {
     return dialog
 });
 
+module.factory('Group', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/groups/:groupId', {
+        realm : '@realm',
+        userId : '@groupId'
+    }, {
+        update : {
+            method : 'PUT'
+        }
+    });
+});
+
+module.factory('GroupChildren', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/groups/:groupId/children', {
+        realm : '@realm',
+        groupId : '@groupId'
+    });
+});
 
+module.factory('Groups', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/groups', {
+        realm : '@realm'
+    });
+});
+
+module.factory('GroupRealmRoleMapping', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/groups/:groupId/role-mappings/realm', {
+        realm : '@realm',
+        groupId : '@groupId'
+    });
+});
+
+module.factory('GroupCompositeRealmRoleMapping', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/groups/:groupId/role-mappings/realm/composite', {
+        realm : '@realm',
+        groupId : '@groupId'
+    });
+});
+
+module.factory('GroupAvailableRealmRoleMapping', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/groups/:groupId/role-mappings/realm/available', {
+        realm : '@realm',
+        groupId : '@groupId'
+    });
+});
 
 
+module.factory('GroupClientRoleMapping', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/groups/:groupId/role-mappings/clients/:client', {
+        realm : '@realm',
+        groupId : '@groupId',
+        client : "@client"
+    });
+});
+
+module.factory('GroupAvailableClientRoleMapping', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/groups/:groupId/role-mappings/clients/:client/available', {
+        realm : '@realm',
+        groupId : '@groupId',
+        client : "@client"
+    });
+});
+
+module.factory('GroupCompositeClientRoleMapping', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/groups/:groupId/role-mappings/clients/:client/composite', {
+        realm : '@realm',
+        groupId : '@groupId',
+        client : "@client"
+    });
+});
+
+module.factory('GroupMembership', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/groups/:groupId/members', {
+        realm : '@realm',
+        groupId : '@groupId'
+    });
+});
+
+
+module.factory('UserGroupMembership', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/users/:userId/groups', {
+        realm : '@realm',
+        userId : '@userId'
+    });
+});
+
+module.factory('UserGroupMapping', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/users/:userId/groups/:groupId', {
+        realm : '@realm',
+        userId : '@userId',
+        groupId : '@groupId'
+    }, {
+        update : {
+            method : 'PUT'
+        }
+    });
+});
+
+module.factory('DefaultGroups', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/default-groups/:groupId', {
+        realm : '@realm',
+        groupId : '@groupId'
+    }, {
+        update : {
+            method : 'PUT'
+        }
+    });
+});
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authentication-flows.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authentication-flows.html
index e055978..a77cf9f 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authentication-flows.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authentication-flows.html
@@ -53,8 +53,8 @@
                         <li data-ng-hide="flow.builtIn"><a href="" ng-click="removeExecution(execution)">Delete</a></li>
                         <li data-ng-hide="flow.builtIn || !execution.authenticationFlow"><a href="" ng-click="addSubFlowExecution(execution)">Add Execution</a></li>
                         <li data-ng-hide="flow.builtIn || !execution.authenticationFlow"><a href="" ng-click="addSubFlow(execution)">Add Flow</a></li>
-                        <li data-ng-show="execution.configurable && execution.authenticationConfig == null"><a href="#/create/authentication/{{realm.realm}}/execution/{{execution.id}}/provider/{{execution.providerId}}">Config</a></li>
-                        <li data-ng-show="execution.configurable && execution.authenticationConfig != null"><a href="#/realms/{{realm.realm}}/authentication/config/{{execution.providerId}}/{{execution.authenticationConfig}}">Config</a></li>
+                        <li data-ng-show="execution.configurable && execution.authenticationConfig == null"><a href="#/create/authentication/{{realm.realm}}/flows/{{flow.id}}/execution/{{execution.id}}/provider/{{execution.providerId}}">Config</a></li>
+                        <li data-ng-show="execution.configurable && execution.authenticationConfig != null"><a href="#/realms/{{realm.realm}}/authentication/flows/{{flow.id}}/config/{{execution.providerId}}/{{execution.authenticationConfig}}">Config</a></li>
                     </ul>
                 </div>
             </td>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authenticator-config.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authenticator-config.html
index 5856806..f587568 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authenticator-config.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authenticator-config.html
@@ -2,6 +2,7 @@
 
     <ol class="breadcrumb">
         <li><a href="#/realms/{{realm.realm}}/authentication/flows">Authentication Flows</a></li>
+        <li><a href="#/realms/{{realm.realm}}/authentication/flows/{{flow.alias}}">{{flow.alias | capitalize}}</a></li>
         <li class="active" data-ng-show="create">Create Authenticator Config</li>
         <li class="active" data-ng-hide="create">{{config.alias}}</li>
     </ol>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials.html
index 96f16ca..b1b1062 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials.html
@@ -28,6 +28,11 @@
     <div data-ng-include="resourceUrl + '/partials/' + clientAuthenticatorConfigPartial">
     </div>
 
+    <hr/>
+
+    <div data-ng-include="resourceUrl + '/partials/client-registration-access-token.html'">
+    </div>
+
 </div>
 
 <kc-menu></kc-menu>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-generic.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-generic.html
index 631939c..1d59078 100644
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-generic.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-generic.html
@@ -1,5 +1,5 @@
 <div>
-    <form class="form-horizontal" name="credentialForm" novalidate kc-read-only="!access.manageClients" data-ng-show="currentAuthenticatorConfigProperties.length > 0" data-ng-controller="ClientGenericCredentialsCtrl">
+    <form class="form-horizontal no-margin-top" name="credentialForm" novalidate kc-read-only="!access.manageClients" data-ng-show="currentAuthenticatorConfigProperties.length > 0" data-ng-controller="ClientGenericCredentialsCtrl">
         <fieldset>
             <kc-provider-config realm="realm" config="client.attributes" properties="currentAuthenticatorConfigProperties"></kc-provider-config>
         </fieldset>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt.html
index aa03203..8c581d7 100644
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt.html
@@ -1,5 +1,5 @@
 <div>
-    <form class="form-horizontal" name="keyForm" novalidate kc-read-only="!access.manageClients" data-ng-controller="ClientSignedJWTCtrl">
+    <form class="form-horizontal no-margin-top" name="keyForm" novalidate kc-read-only="!access.manageClients" data-ng-controller="ClientSignedJWTCtrl">
         <div class="form-group">
             <label class="col-md-2 control-label" for="signingCert">{{:: 'certificate' | translate}}</label>
             <kc-tooltip>{{:: 'certificate.tooltip' | translate}}</kc-tooltip>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-secret.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-secret.html
index 2bd53db..744ea80 100644
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-secret.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-secret.html
@@ -1,5 +1,5 @@
 <div>
-    <form class="form-horizontal" name="credentialForm" novalidate kc-read-only="!access.manageClients" data-ng-controller="ClientSecretCtrl">
+    <form class="form-horizontal no-margin-top" name="credentialForm" novalidate kc-read-only="!access.manageClients" data-ng-controller="ClientSecretCtrl">
         <div class="form-group">
             <label class="col-md-2 control-label" for="secret">{{:: 'secret' | translate}}</label>
             <div class="col-sm-6">
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-initial-access.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-initial-access.html
new file mode 100755
index 0000000..7b7c90e
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-initial-access.html
@@ -0,0 +1,52 @@
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+    <kc-tabs-realm></kc-tabs-realm>
+
+    <table class="table table-striped table-bordered">
+        <thead>
+        <tr>
+            <th class="kc-table-actions" colspan="6">
+                <div class="form-inline">
+                    <div class="form-group">
+                        <div class="input-group">
+                            <input type="text" placeholder="{{:: 'search.placeholder' | translate}}" data-ng-model="search.id" class="form-control search" onkeyup="if(event.keyCode == 13){$(this).next('I').click();}">
+                            <div class="input-group-addon">
+                                <i class="fa fa-search" type="submit"></i>
+                            </div>
+                        </div>
+                    </div>
+
+                    <div class="pull-right" data-ng-show="access.manageRealm">
+                        <a id="createClient" class="btn btn-default" href="#/realms/{{realm.realm}}/client-initial-access/create">{{:: 'create' | translate}}</a>
+                    </div>
+                </div>
+            </th>
+        </tr>
+        <tr data-ng-hide="clients.length == 0">
+            <th>{{:: 'id' | translate}}</th>
+            <th>{{:: 'created' | translate}}</th>
+            <th>{{:: 'expires' | translate}}</th>
+            <th>{{:: 'count' | translate}}</th>
+            <th>{{:: 'remainingCount' | translate}}</th>
+            <th>{{:: 'actions' | translate}}</th>
+        </tr>
+        </thead>
+        <tbody>
+        <tr ng-repeat="ia in clientInitialAccess | filter:search | orderBy:'timestamp'">
+            <td>{{ia.id}}</td>
+            <td>{{(ia.timestamp * 1000)|date:'shortDate'}}&nbsp;{{(ia.timestamp * 1000)|date:'mediumTime'}}</td>
+            <td><span data-ng-show="ia.expiration > 0">{{((ia.timestamp + ia.expiration) * 1000)|date:'shortDate'}}&nbsp;{{((ia.timestamp + ia.expiration) * 1000)|date:'mediumTime'}}</span></td>
+            <td>{{ia.count}}</td>
+            <td>{{ia.remainingCount}}</td>
+            <td class="kc-action-cell">
+                <button class="btn btn-default btn-block btn-sm" data-ng-click="remove(ia.id)">{{:: 'delete' | translate}}</button>
+            </td>
+        </tr>
+        <tr data-ng-show="(clients | filter:search).length == 0">
+            <td class="text-muted" colspan="3" data-ng-show="search.clientId">{{:: 'no-results' | translate}}</td>
+            <td class="text-muted" colspan="3" data-ng-hide="search.clientId">{{:: 'no-clients-available' | translate}}</td>
+        </tr>
+        </tbody>
+    </table>
+</div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-initial-access-create.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-initial-access-create.html
new file mode 100755
index 0000000..ef54939
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-initial-access-create.html
@@ -0,0 +1,63 @@
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+
+    <ol class="breadcrumb">
+        <li><a href="#/realms/{{realm.realm}}/client-initial-access">{{:: 'initial-access-tokens' | translate}}</a></li>
+        <li>{{:: 'add-initial-access-tokens' | translate}}</li>
+    </ol>
+
+    <h1 data-ng-show="create">{{:: 'add-client' | translate}}</h1>
+
+    <form class="form-horizontal" name="createForm" novalidate kc-read-only="!access.manageRealm" data-ng-hide="token">
+
+        <div class="form-group">
+            <label class="col-md-2 control-label" for="expiration">{{:: 'expiration' | translate}}</label>
+
+            <div class="col-md-6 time-selector">
+                <input class="form-control" type="number" required min="0" max="31536000" data-ng-model="expiration" id="expiration"
+                       name="expiration"/>
+                <select class="form-control" name="expirationUnit" data-ng-model="expirationUnit">
+                    <option data-ng-selected="!expirationUnit" value="Seconds">{{:: 'seconds' | translate}}</option>
+                    <option value="Minutes">{{:: 'minutes' | translate}}</option>
+                    <option value="Hours">{{:: 'hours' | translate}}</option>
+                    <option value="Days">{{:: 'days' | translate}}</option>
+                </select>
+            </div>
+            <kc-tooltip>{{:: 'expiration.tooltip' | translate}}</kc-tooltip>
+        </div>
+
+        <div class="form-group">
+            <label class="col-md-2 control-label" for="count">{{:: 'count' | translate}} </label>
+            <div class="col-sm-6">
+                <input class="form-control" type="text" id="count" name="count" required min="1" max="100" data-ng-model="count">
+            </div>
+            <kc-tooltip>{{:: 'count.tooltip' | translate}}</kc-tooltip>
+        </div>
+
+        <div class="form-group">
+            <div class="col-md-10 col-md-offset-2" data-ng-show="access.manageRealm">
+                <button kc-save>{{:: 'save' | translate}}</button>
+                <button kc-cancel data-ng-click="cancel()">{{:: 'cancel' | translate}}</button>
+            </div>
+        </div>
+    </form>
+
+    <form name="displayForm" data-ng-show="token">
+        <div class="form-group">
+            <label for="initialAccessToken">{{:: 'initial-access-token' | translate}}</label>
+
+            <div>
+                <textarea type="text" id="initialAccessToken" name="initialAccessToken" class="form-control" rows="6" kc-select-action="click">{{token}}</textarea>
+            </div>
+
+            <kc-tooltip>{{:: 'initial-access.copyPaste.tooltip' | translate}}</kc-tooltip>
+        </div>
+
+        <div class="form-group">
+            <div>
+                <button class="btn btn-default" data-ng-click="done()">{{:: 'Back' | translate}}</button>
+            </div>
+        </div>
+    </form>
+</div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-mappers-add.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-mappers-add.html
index 4237f7d..04c2339 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-mappers-add.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-mappers-add.html
@@ -37,7 +37,7 @@
             <td>{{mapper.name}}</td>
             <td>{{mapperTypes[mapper.protocolMapper].category}}</td>
             <td>{{mapperTypes[mapper.protocolMapper].name}}</td>
-            <td><input type="checkbox" ng-model="mapper.isChecked"></td>
+            <td><input type="checkbox" ng-model="mapper.isChecked" id="{{mapper.protocolMapper}}"></td>
         </tr>
         <tr data-ng-show="mappers.length == 0">
             <td>{{:: 'no-mappers-available' | translate}}</td>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-registration-access-token.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-registration-access-token.html
new file mode 100644
index 0000000..55a3546
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-registration-access-token.html
@@ -0,0 +1,18 @@
+<div>
+    <form class="form-horizontal" name="registrationAccessTokenForm" novalidate kc-read-only="!access.manageClients">
+        <div class="form-group">
+            <label class="col-md-2 control-label" for="registrationAccessToken">{{:: 'registrationAccessToken' | translate}}</label>
+            <div class="col-sm-6">
+                <div class="row">
+                    <div class="col-sm-6">
+                        <input readonly kc-select-action="click" class="form-control" type="text" id="registrationAccessToken" name="registrationAccessToken" data-ng-model="client.registrationAccessToken">
+                    </div>
+                    <div class="col-sm-6" data-ng-show="access.manageClients">
+                        <button type="submit" data-ng-click="regenerateRegistrationAccessToken()" class="btn btn-default">{{:: 'registrationAccessToken.regenerate' | translate}}</button>
+                    </div>
+                </div>
+            </div>
+            <kc-tooltip>{{:: 'registrationAccessToken.tooltip' | translate}}</kc-tooltip>
+        </div>
+    </form>
+</div>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-execution.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-execution.html
index 53c82d1..78eb532 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-execution.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-execution.html
@@ -11,7 +11,7 @@
                 <div>
                     <select class="form-control" id="provider"
                             ng-model="provider"
-                            ng-options="provider.id for provider in providers">
+                            ng-options="provider.displayName|capitalize for provider in providers">
                     </select>
                 </div>
             </div>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-flow-execution.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-flow-execution.html
index e884ef8..e29eef4 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-flow-execution.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-flow-execution.html
@@ -7,7 +7,7 @@
         <div class="form-group">
             <label class="col-md-2 control-label" for="alias">Alias </label>
             <div class="col-sm-6">
-                <input class="form-control" type="text" id="alias" name="alias" data-ng-model="flow.alias" autofocus>
+                <input class="form-control" type="text" id="alias" name="alias" data-ng-model="flow.alias" autofocus required>
             </div>
             <kc-tooltip>Specifies display name for the flow.</kc-tooltip>
         </div>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-group.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-group.html
new file mode 100755
index 0000000..6e47880
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-group.html
@@ -0,0 +1,25 @@
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+    <div>
+        <h1>Create Group</h1>
+    </div>
+    <form class="form-horizontal" name="clientForm" novalidate>
+        <fieldset class="border-top">
+            <div class="form-group">
+                <label class="col-md-2 control-label"for="name">Name <span class="required">*</span></label>
+                <div class="col-md-6">
+                    <input class="form-control" type="text" id="name" name="name" data-ng-model="group.name" autofocus
+                           required >
+                </div>
+            </div>
+        </fieldset>
+
+        <div class="form-group">
+            <div class="col-md-10 col-md-offset-2">
+                <button kc-save>Save</button>
+                <button kc-cancel data-ng-click="cancel()">Cancel</button>
+            </div>
+        </div>
+    </form>
+</div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/default-groups.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/default-groups.html
new file mode 100755
index 0000000..bcfd547
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/default-groups.html
@@ -0,0 +1,80 @@
+ <div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+     <kc-tabs-group-list></kc-tabs-group-list>
+
+     <form class="form-horizontal" name="realmForm" novalidate>
+         <div class="form-group" kc-read-only="!access.manageRealm">
+             <label class="col-md-1 control-label" class="control-label"></label>
+
+             <div class="col-md-8" >
+                  <div class="row">
+                     <div class="col-md-5">
+                         <table class="table table-striped table-bordered">
+                             <thead>
+                             <tr>
+                                 <th class="kc-table-actions" colspan="5">
+                                     <div class="form-inline">
+                                         <label class="control-label">Default Groups</label>
+                                         <kc-tooltip>Newly created or registered users will automatically be added to these groups</kc-tooltip>
+
+                                         <div class="pull-right" data-ng-show="access.manageRealm">
+                                             <button id="removeDefaultGroup" class="btn btn-default" ng-click="removeDefaultGroup()">Remove</button>
+                                         </div>
+                                     </div>
+                                 </th>
+                             </tr>
+                             </thead>
+                             <tbody>
+                             <tr>
+                                 <td>
+                                     <select id="defaultGroups" class="form-control" size=5
+                                                            ng-model="selectedGroup"
+                                                            ng-options="r.path for r in defaultGroups">
+                                         <option style="display:none" value="">select a type</option>
+                                 </select>
+
+
+                                 </td>
+                             </tr>
+                             </tbody>
+                         </table>
+                     </div>
+                     <div class="col-md-5">
+                         <table class="table table-striped table-bordered">
+                             <thead>
+                             <tr>
+                                 <th class="kc-table-actions" colspan="5">
+
+                                     <div class="form-inline">
+                                         <label class="control-label">Available Groups</label>
+                                         <kc-tooltip>Select a group you want to add as a default.</kc-tooltip>
+
+                                         <div class="pull-right" data-ng-show="access.manageRealm">
+                                             <button id="addDefaultGroup" class="btn btn-default" ng-click="addDefaultGroup()">Add</button>
+                                         </div>
+                                     </div>
+                                 </th>
+                             </tr>
+                             </thead>
+                         <tbody>
+                             <tr>
+                                 <td>                             <div
+                                         tree-id="tree"
+                                         angular-treeview="true"
+                                         tree-model="groupList"
+                                         node-id="id"
+                                         node-label="name"
+                                         node-children="subGroups" >
+                                 </div>
+
+                                 </td>
+                             </tr>
+                             </tbody>
+                         </table>
+                     </div>
+                 </div>
+             </div>
+         </div>
+     </form>
+ </div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-attributes.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-attributes.html
new file mode 100755
index 0000000..9fcfc8b
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-attributes.html
@@ -0,0 +1,45 @@
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+    <ol class="breadcrumb">
+        <li><a href="#/realms/{{realm.realm}}/groups">Groups</a></li>
+        <li>{{group.name}}</li>
+    </ol>
+
+    <kc-tabs-group></kc-tabs-group>
+
+    <form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageUsers">
+        <table class="table table-striped table-bordered">
+            <thead>
+            <tr>
+                <th>Key</th>
+                <th>Value</th>
+                <th>Actions</th>
+            </tr>
+            </thead>
+            <tbody>
+            <tr ng-repeat="(key, value) in group.attributes">
+                <td>{{key}}</td>
+                <td><input ng-model="group.attributes[key]" class="form-control" type="text" name="{{key}}" id="attribute-{{key}}" /></td>
+                <td class="kc-action-cell">
+                    <button class="btn btn-default btn-block btn-sm" data-ng-click="removeAttribute(key)">Delete</button>
+                </td>
+            </tr>
+            <tr>
+                <td><input ng-model="newAttribute.key" class="form-control" type="text" id="newAttributeKey" /></td>
+                <td><input ng-model="newAttribute.value" class="form-control" type="text" id="newAttributeValue" /></td>
+                <td class="kc-action-cell">
+                    <button class="btn btn-default btn-block btn-sm" data-ng-click="addAttribute()">Add</button>
+                </td>
+            </tr>
+            </tbody>
+        </table>
+
+        <div class="form-group" data-ng-show="access.manageUsers">
+            <div class="col-md-12">
+                <button kc-save  data-ng-disabled="!changed">Save</button>
+                <button kc-reset data-ng-disabled="!changed">Cancel</button>
+            </div>
+        </div>
+    </form>
+</div>
+
+<kc-menu></kc-menu>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-detail.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-detail.html
new file mode 100755
index 0000000..3dc41aa
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-detail.html
@@ -0,0 +1,28 @@
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+    <ol class="breadcrumb">
+        <li><a href="#/realms/{{realm.realm}}/groups">Groups</a></li>
+        <li>{{group.name}}</li>
+    </ol>
+
+    <kc-tabs-group></kc-tabs-group>
+    <form class="form-horizontal" name="clientForm" novalidate>
+        <fieldset class="border-top">
+            <div class="form-group">
+                <label class="col-md-2 control-label"for="name">Name <span class="required">*</span></label>
+                <div class="col-md-6">
+                    <input class="form-control" type="text" id="name" name="name" data-ng-model="group.name" autofocus
+                           required >
+                </div>
+            </div>
+        </fieldset>
+
+        <div class="form-group" data-ng-show="access.manageUsers">
+            <div class="col-md-10 col-md-offset-2">
+                <button kc-save data-ng-disabled="!changed">Save</button>
+                <button kc-reset data-ng-disabled="!changed" data-ng-click="cancel()">Cancel</button>
+            </div>
+        </div>
+    </form>
+</div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-list.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-list.html
new file mode 100755
index 0000000..ac8267c
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-list.html
@@ -0,0 +1,37 @@
+ <div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+     <kc-tabs-group-list></kc-tabs-group-list>
+
+    <table class="table table-striped table-bordered">
+         <thead>
+         <tr>
+             <th class="kc-table-actions" colspan="5">
+                 <div class="form-inline">
+                      <div class="pull-right" data-ng-show="access.manageUsers">
+                          <button id="createGroup" class="btn btn-default" ng-click="createGroup(tree.currentNode)">New</button>
+                          <button id="editGroup" ng-disabled="isDisabled()" class="btn btn-default" ng-click="edit(tree.currentNode)">Edit</button>
+                          <button id="cutGroup" ng-disabled="isDisabled()" class="btn btn-default" ng-click="cut(tree.currentNode)">Cut</button>
+                          <button id="pasteGroup" ng-disabled="!cutNode" class="btn btn-default" ng-click="paste(tree.currentNode)">Paste</button>
+                          <button id="removeGroup" ng-disabled="isDisabled()" class="btn btn-default" ng-click="remove(tree.currentNode)">Delete</button>
+                     </div>
+                 </div>
+             </th>
+         </tr>
+         </thead>
+         <tbody>
+         <tr>
+             <td>    <div
+                     tree-id="tree"
+                     angular-treeview="true"
+                     tree-model="groupList"
+                     node-id="id"
+                     node-label="name"
+                     node-children="subGroups" >
+             </div>
+             </td>
+         </tr>
+         </tbody>
+     </table>
+
+ </div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-members.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-members.html
new file mode 100755
index 0000000..50bc11b
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-members.html
@@ -0,0 +1,50 @@
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+    <ol class="breadcrumb">
+        <li><a href="#/realms/{{realm.realm}}/groups">Groups</a></li>
+        <li>{{group.name}}</li>
+    </ol>
+    <kc-tabs-group></kc-tabs-group>
+
+    <table class="table table-striped table-bordered">
+        <caption data-ng-show="users" class="hidden">Table of group members</caption>
+        <thead>
+         <tr>
+        <tr data-ng-show="searchLoaded && users.length > 0">
+            <th>Username</th>
+            <th>Last Name</th>
+            <th>First Name</th>
+            <th>Email</th>
+            <th></th>
+        </tr>
+        </tr>
+        </thead>
+        <tfoot data-ng-show="users && (users.length >= query.max || query.first > 0)">
+        <tr>
+            <td colspan="7">
+                <div class="table-nav">
+                    <button data-ng-click="firstPage()" class="first" ng-disabled="query.first == 0">First page</button>
+                    <button data-ng-click="previousPage()" class="prev" ng-disabled="query.first == 0">Previous page</button>
+                    <button data-ng-click="nextPage()" class="next" ng-disabled="users.length < query.max">Next page</button>
+                </div>
+            </td>
+        </tr>
+        </tfoot>
+        <tbody>
+        <tr ng-repeat="user in users">
+            <td><a href="#/realms/{{realm.realm}}/users/{{user.id}}">{{user.username}}</a></td>
+            <td>{{user.lastName}}</td>
+            <td>{{user.firstName}}</td>
+            <td>{{user.email}}</td>
+            <td class="kc-action-cell">
+                <button class="btn btn-default btn-block btn-sm" kc-open="/realms/{{realm.realm}}/users/{{user.id}}">Edit</button>
+            </td>
+        </tr>
+        <tr data-ng-show="!users || users.length == 0">
+            <td class="text-muted" data-ng-show="searchLoaded && users.length == 0 && lastSearch != null">No group members</td>
+            <td class="text-muted" data-ng-show="searchLoaded && users.length == 0 && lastSearch == null">No group members</td>
+        </tr>
+        </tbody>
+    </table>
+</div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-role-mappings.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-role-mappings.html
new file mode 100755
index 0000000..22916e1
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-role-mappings.html
@@ -0,0 +1,101 @@
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+    <ol class="breadcrumb">
+        <li><a href="#/realms/{{realm.realm}}/groups">Groups</a></li>
+        <li>{{group.name}}</li>
+    </ol>
+
+    <kc-tabs-group></kc-tabs-group>
+
+    <form class="form-horizontal" name="realmForm" novalidate>
+        <div class="form-group" kc-read-only="!access.manageUsers">
+            <label class="col-md-2 control-label" class="control-label">Realm Roles</label>
+
+            <div class="col-md-10">
+                <div class="row">
+                    <div class="col-md-3">
+                        <label class="control-label" for="available">Available Roles</label>
+                        <select id="available" class="form-control" multiple size="5"
+                                ng-multiple="true"
+                                ng-model="selectedRealmRoles"
+                                ng-options="r.name for r in realmRoles">
+                        </select>
+                        <button ng-disabled="selectedRealmRoles.length == 0" ng-disabled="c.length == 0" class="btn btn-default" type="submit" ng-click="addRealmRole()">
+                            Add selected <i class="fa fa-angle-right"></i>
+                        </button>
+                        <kc-tooltip>Realm roles that can be assigned to the group.</kc-tooltip>
+                    </div>
+                    <div class="col-md-3">
+                        <label class="control-label" for="assigned">Assigned Roles</label>
+                        <kc-tooltip>Realm roles mapped to the group</kc-tooltip>
+                        <select id="assigned" class="form-control" multiple size=5
+                                ng-multiple="true"
+                                ng-model="selectedRealmMappings"
+                                ng-options="r.name for r in realmMappings">
+                        </select>
+                        <button ng-disabled="selectedRealmMappings.length == 0" class="btn btn-default" type="submit" ng-click="deleteRealmRole()">
+                            <i class="fa fa-angle-double-left"></i> Remove selected
+                        </button>
+                    </div>
+                    <div class="col-md-3">
+                        <label class="control-label" for="realm-composite">Effective Roles</label>
+                        <kc-tooltip>All realm role mappings.  Some roles here might be inherited from a mapped composite role.</kc-tooltip>
+                        <select id="realm-composite" class="form-control" multiple size=5
+                                disabled="true"
+                                ng-model="dummymodel"
+                                ng-options="r.name for r in realmComposite">
+                        </select>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <div class="form-group">
+                <label class="col-md-2 control-label" class="control-label">
+                    <span>Client Roles</span>
+                    <select class="form-control" id="clients" name="clients" ng-change="changeClient()" ng-model="targetClient" ng-options="a.clientId for a in clients" ng-disabled="false"></select>
+                </label>
+                <div class="col-md-10" kc-read-only="!access.manageUsers">
+                    <div class="row" data-ng-hide="targetClient">
+                        <div class="col-md-4"><span class="text-muted">Select client to view roles for client</span></div>
+                    </div>
+                    <div class="row" data-ng-show="targetClient">
+                        <div class="col-md-3">
+                            <label class="control-label" for="available-client">Available Roles</label>
+                            <kc-tooltip>Assignable roles from this client.</kc-tooltip>
+                            <select id="available-client" class="form-control" multiple size="5"
+                                    ng-multiple="true"
+                                    ng-model="selectedClientRoles"
+                                    ng-options="r.name for r in clientRoles">
+                            </select>
+                            <button ng-disabled="selectedClientRoles.length == 0" class="btn btn-default" type="submit" ng-click="addClientRole()">
+                                Add selected <i class="fa fa-angle-double-right"></i>
+                            </button>
+                        </div>
+                        <div class="col-md-3">
+                            <label class="control-label" for="assigned-client">Assigned Roles</label>
+                            <kc-tooltip>Role mappings for this client.</kc-tooltip>
+                            <select id="assigned-client" class="form-control" multiple size=5
+                                    ng-multiple="true"
+                                    ng-model="selectedClientMappings"
+                                    ng-options="r.name for r in clientMappings">
+                            </select>
+                            <button ng-disabled="selectedClientMappings.length == 0" class="btn btn-default" type="submit" ng-click="deleteClientRole()">
+                                <i class="fa fa-angle-double-left"></i> Remove selected
+                            </button>
+                        </div>
+                        <div class="col-md-3">
+                            <label class="control-label" for="client-composite">Effective Roles <span tooltip-placement="right" tooltip-trigger="mouseover mouseout" tooltip="Role mappings for this client.  Some roles here might be inherited from a mapped composite role." class="fa fa-info-circle"></span></label>
+                            <select id="client-composite" class="form-control" multiple size=5
+                                    disabled="true"
+                                    ng-model="dummymodel"
+                                    ng-options="r.name for r in clientComposite">
+                            </select>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </form>
+</div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/otp-policy.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/otp-policy.html
index 85ad20b..eaaf811 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/otp-policy.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/otp-policy.html
@@ -46,23 +46,23 @@
         <div class="form-group">
             <label class="col-md-2 control-label" for="lookAhead">Look ahead window</label>
             <div class="col-md-6">
-                <input class="form-control" type="text" id="lookAhead" name="lookAhead" data-ng-model="realm.otpPolicyLookAheadWindow" autofocus>
+                <input class="form-control" type="number" required min="1" max="120" id="lookAhead" name="lookAhead" data-ng-model="realm.otpPolicyLookAheadWindow" autofocus>
             </div>
             <kc-tooltip>How far ahead should the server look just in case the token generator and server are out of time sync or counter sync?</kc-tooltip>
         </div>
 
-        <div class="form-group" data-ng-show="realm.otpPolicyType == 'hotp'">
+        <div class="form-group" data-ng-if="realm.otpPolicyType == 'hotp'">
             <label class="col-md-2 control-label" for="counter">Initial Counter</label>
             <div class="col-md-6">
-                <input class="form-control" type="text" id="counter" name="counter" data-ng-model="realm.otpPolicyInitialCounter" autofocus>
+                <input class="form-control" type="number" data-ng-required="realm.otpPolicyType == 'hotp'" min="1" max="120" id="counter" name="counter" data-ng-model="realm.otpPolicyInitialCounter" autofocus>
             </div>
             <kc-tooltip>What should the initial counter value be?</kc-tooltip>
         </div>
 
-        <div class="form-group" data-ng-show="realm.otpPolicyType == 'totp'">
+        <div class="form-group" data-ng-if="realm.otpPolicyType == 'totp'">
             <label class="col-md-2 control-label" for="counter">OTP Token Period</label>
             <div class="col-md-6">
-                <input class="form-control" type="text" id="period" name="period" data-ng-model="realm.otpPolicyPeriod">
+                <input class="form-control" type="number" data-ng-required="realm.otpPolicyType == 'totp'" min="1" max="120" id="period" name="period" data-ng-model="realm.otpPolicyPeriod">
             </div>
             <kc-tooltip>How many seconds should an OTP token be valid? Defaults to 30 seconds.</kc-tooltip>
         </div>
@@ -79,4 +79,4 @@
 </div>
 
 
-<kc-menu></kc-menu>
\ No newline at end of file
+<kc-menu></kc-menu>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html
index 170bfe6..4cbd12a 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html
@@ -1,4 +1,4 @@
-<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2" data-ng-init="initProvider()">
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
     <ol class="breadcrumb">
         <li><a href="#/realms/{{realm.realm}}/identity-provider-settings">{{:: 'identity-providers' | translate}}</a></li>
         <li>{{identityProvider.alias}}</li>
@@ -53,19 +53,6 @@
                 <kc-tooltip>{{:: 'identity-provider.stored-tokens-readable.tooltip' | translate}}</kc-tooltip>
             </div>
             <div class="form-group">
-                <label class="col-md-2 control-label" for="updateProfileFirstLoginMode">{{:: 'update-profile-on-first-login' | translate}}</label>
-                <div class="col-md-2">
-                		<div>
-                        <select id="updateProfileFirstLoginMode" ng-model="identityProvider.updateProfileFirstLoginMode" class="form-control">
-                            <option value="on">{{:: 'on' | translate}}</option>
-                            <option value="missing">{{:: 'on-missing-info' | translate}}</option>
-                            <option value="off">{{:: 'off' | translate}}</option>
-                        </select>
-                    </div>
-                </div>
-                <kc-tooltip>{{:: 'update-profile-on-first-login.tooltip' | translate}}</kc-tooltip>
-            </div>
-            <div class="form-group">
                 <label class="col-md-2 control-label" for="trustEmail">{{:: 'trust-email' | translate}}</label>
                 <div class="col-md-6">
                     <input ng-model="identityProvider.trustEmail" name="identityProvider.trustEmail" id="trustEmail" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
@@ -79,6 +66,19 @@
                 </div>
                 <kc-tooltip>{{:: 'gui-order.tooltip' | translate}}</kc-tooltip>
             </div>
+            <div class="form-group">
+                <label class="col-md-2 control-label" for="firstBrokerLoginFlowAlias">{{:: 'first-broker-login-flow' | translate}}</label>
+                <div class="col-md-6">
+                    <div>
+                        <select class="form-control" id="firstBrokerLoginFlowAlias"
+                                ng-model="identityProvider.firstBrokerLoginFlowAlias"
+                                ng-options="flow.alias as flow.alias for flow in authFlows"
+                                required>
+                        </select>
+                    </div>
+                </div>
+                <kc-tooltip>{{:: 'first-broker-login-flow.tooltip' | translate}}</kc-tooltip>
+            </div>
         </fieldset>
         <fieldset>
             <legend uncollapsed><span class="text">{{:: 'openid-connect-config' | translate}}</span> <kc-tooltip>{{:: 'openid-connect-config.tooltip' | translate}}</kc-tooltip></legend>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html
index bb9726f..c8f6c37 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html
@@ -53,19 +53,6 @@
                 <kc-tooltip>{{:: 'identity-provider.stored-tokens-readable.tooltip' | translate}}</kc-tooltip>
             </div>
             <div class="form-group">
-                <label class="col-md-2 control-label" for="updateProfileFirstLoginMode">{{:: 'update-profile-on-first-login' | translate}}</label>
-                <div class="col-md-2">
-                		<div>
-                        <select id="updateProfileFirstLoginMode" ng-model="identityProvider.updateProfileFirstLoginMode" class="form-control">
-                            <option value="on">{{:: 'on' | translate}}</option>
-                            <option value="missing">{{:: 'on-missing-info' | translate}}</option>
-                            <option value="off">{{:: 'off' | translate}}</option>
-                        </select>
-                    </div>
-                </div>
-                <kc-tooltip>{{:: 'update-profile-on-first-login.tooltip' | translate}}</kc-tooltip>
-            </div>
-            <div class="form-group">
                 <label class="col-md-2 control-label" for="trustEmail">{{:: 'trust-email' | translate}}</label>
                 <div class="col-md-6">
                     <input ng-model="identityProvider.trustEmail" name="identityProvider.trustEmail" id="trustEmail" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
@@ -79,6 +66,19 @@
                 </div>
                 <kc-tooltip>{{:: 'gui-order.tooltip' | translate}}</kc-tooltip>
             </div>
+            <div class="form-group">
+                <label class="col-md-2 control-label" for="firstBrokerLoginFlowAlias">{{:: 'first-broker-login-flow' | translate}}</label>
+                <div class="col-md-6">
+                    <div>
+                        <select class="form-control" id="firstBrokerLoginFlowAlias"
+                                ng-model="identityProvider.firstBrokerLoginFlowAlias"
+                                ng-options="flow.alias as flow.alias for flow in authFlows"
+                                required>
+                        </select>
+                    </div>
+                </div>
+                <kc-tooltip>{{:: 'first-broker-login-flow.tooltip' | translate}}</kc-tooltip>
+            </div>
         </fieldset>
         <fieldset>
             <legend uncollapsed><span class="text">{{:: 'saml-config' | translate}}</span> <kc-tooltip>{{:: 'identity-provider.saml-config.tooltip' | translate}}</kc-tooltip></legend>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-social.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-social.html
index 2f71d19..5897f99 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-social.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-social.html
@@ -1,4 +1,4 @@
-<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2" data-ng-init="initProvider()">
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
     <ol class="breadcrumb">
         <li><a href="#/realms/{{realm.realm}}/identity-provider-settings">{{:: 'identity-providers' | translate}}</a></li>
         <li>{{identityProvider.alias}}</li>
@@ -64,19 +64,6 @@
                 <kc-tooltip>{{:: 'identity-provider.enabled.tooltip' | translate}}</kc-tooltip>
             </div>
             <div class="form-group">
-                <label class="col-md-2 control-label" for="updateProfileFirstLoginMode">{{:: 'update-profile-on-first-login' | translate}}</label>
-                <div class="col-md-2">
-                		<div>
-                        <select id="updateProfileFirstLoginMode" ng-model="identityProvider.updateProfileFirstLoginMode" class="form-control">
-                            <option value="on">{{:: 'on' | translate}}</option>
-                            <option value="missing">{{:: 'on-missing-info' | translate}}</option>
-                            <option value="off">{{:: 'off' | translate}}</option>
-                        </select>
-                    </div>
-                </div>
-                <kc-tooltip>{{:: 'update-profile-on-first-login.tooltip' | translate}}</kc-tooltip>
-            </div>
-            <div class="form-group">
                 <label class="col-md-2 control-label" for="trustEmail">{{:: 'trust-email' | translate}}</label>
                 <div class="col-md-6">
                     <input ng-model="identityProvider.trustEmail" name="identityProvider.trustEmail" id="trustEmail" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
@@ -97,6 +84,19 @@
                 </div>
                 <kc-tooltip>{{:: 'gui-order.tooltip' | translate}}</kc-tooltip>
             </div>
+            <div class="form-group">
+                <label class="col-md-2 control-label" for="firstBrokerLoginFlowAlias">{{:: 'first-broker-login-flow' | translate}}</label>
+                <div class="col-md-6">
+                    <div>
+                        <select class="form-control" id="firstBrokerLoginFlowAlias"
+                                ng-model="identityProvider.firstBrokerLoginFlowAlias"
+                                ng-options="flow.alias as flow.alias for flow in authFlows"
+                                required>
+                        </select>
+                    </div>
+                </div>
+                <kc-tooltip>{{:: 'first-broker-login-flow.tooltip' | translate}}</kc-tooltip>
+            </div>
         </fieldset>
 
         <div class="form-group">
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/required-actions.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/required-actions.html
index 0161a30..8a81237 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/required-actions.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/required-actions.html
@@ -20,8 +20,8 @@
         <tbody>
         <tr ng-repeat="requiredAction in requiredActions" data-ng-show="requiredActions.length > 0">
             <td>{{requiredAction.name}}</td>
-            <td><input type="checkbox" ng-model="requiredAction.enabled" ng-change="updateRequiredAction(requiredAction)"></td>
-            <td><input type="checkbox" ng-model="requiredAction.defaultAction" ng-change="updateRequiredAction(requiredAction)"></td>
+            <td><input type="checkbox" ng-model="requiredAction.enabled" ng-change="updateRequiredAction(requiredAction)" id="{{requiredAction.alias}}.enabled"></td>
+            <td><input type="checkbox" ng-model="requiredAction.defaultAction" ng-change="updateRequiredAction(requiredAction)" id="{{requiredAction.alias}}.defaultAction"></td>
         </tr>
         <tr data-ng-show="requiredActions.length == 0">
             <td>No required actions configured</td>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html
index 7904e56..d81bea7 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html
@@ -94,7 +94,6 @@
                     </div>
                 </div>
             </div>
-        </div>
     </form>
 </div>
 
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-group-membership.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-group-membership.html
new file mode 100755
index 0000000..a8e2c13
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-group-membership.html
@@ -0,0 +1,85 @@
+ <div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+     <ol class="breadcrumb">
+         <li><a href="#/realms/{{realm.realm}}/users">Users</a></li>
+         <li>{{user.username}}</li>
+     </ol>
+
+     <kc-tabs-user></kc-tabs-user>
+
+     <form class="form-horizontal" name="realmForm" novalidate>
+         <div class="form-group" kc-read-only="!access.manageUsers">
+             <label class="col-md-1 control-label" class="control-label"></label>
+
+             <div class="col-md-8" >
+                  <div class="row">
+                     <div class="col-md-5">
+                         <table class="table table-striped table-bordered">
+                             <thead>
+                             <tr>
+                                 <th class="kc-table-actions" colspan="5">
+                                     <div class="form-inline">
+                                         <label class="control-label">Group Membership</label>
+                                         <kc-tooltip>Groups user is a member of.  Select a listed group and click the Leave button to leave the group.</kc-tooltip>
+
+                                         <div class="pull-right" data-ng-show="access.manageUsers">
+                                             <button id="leaveGroups" class="btn btn-default" ng-click="leaveGroup()">Leave</button>
+                                         </div>
+                                     </div>
+                                 </th>
+                             </tr>
+                             </thead>
+                             <tbody>
+                             <tr>
+                                 <td>
+                                     <select id="groupMembership" class="form-control" size=5
+                                                            ng-model="selectedGroup"
+                                                            ng-options="r.path for r in groupMemberships">
+                                         <option style="display:none" value="">select a type</option>
+                                 </select>
+
+
+                                 </td>
+                             </tr>
+                             </tbody>
+                         </table>
+                     </div>
+                     <div class="col-md-5">
+                         <table class="table table-striped table-bordered">
+                             <thead>
+                             <tr>
+                                 <th class="kc-table-actions" colspan="5">
+
+                                     <div class="form-inline">
+                                         <label class="control-label">Available Groups</label>
+                                         <kc-tooltip>Groups a user can join.  Select a group and click the join button.</kc-tooltip>
+
+                                         <div class="pull-right" data-ng-show="access.manageUsers">
+                                             <button id="joinGroup" class="btn btn-default" ng-click="joinGroup()">Join</button>
+                                         </div>
+                                     </div>
+                                 </th>
+                             </tr>
+                             </thead>
+                         <tbody>
+                             <tr>
+                                 <td>                             <div
+                                         tree-id="tree"
+                                         angular-treeview="true"
+                                         tree-model="groupList"
+                                         node-id="id"
+                                         node-label="name"
+                                         node-children="subGroups" >
+                                 </div>
+
+                                 </td>
+                             </tr>
+                             </tbody>
+                         </table>
+                     </div>
+                 </div>
+             </div>
+         </div>
+     </form>
+ </div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html
index baa3b45..8da0e56 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html
@@ -40,7 +40,8 @@
     <div class="nav-category" data-ng-show="current.realm">
         <h2>Manage</h2>
         <ul class="nav nav-pills nav-stacked">
-            <li data-ng-show="access.viewUsers" data-ng-class="(path[2] == 'users' || path[1] == 'user') && 'active'"><a href="#/realms/{{realm.realm}}/users"><span class="pficon pficon-users"></span> Users</a></li>
+            <li data-ng-show="access.viewUsers" data-ng-class="(path[2] == 'groups' || path[1] == 'group') && 'active'"><a href="#/realms/{{realm.realm}}/groups"><span class="pficon pficon-users"></span> Groups</a></li>
+            <li data-ng-show="access.viewUsers" data-ng-class="(path[2] == 'users' || path[1] == 'user') && 'active'"><a href="#/realms/{{realm.realm}}/users"><span class="pficon pficon-user"></span> Users</a></li>
             <li data-ng-show="access.viewRealm" data-ng-class="(path[2] == 'sessions') && 'active'"><a href="#/realms/{{realm.realm}}/sessions/realm"><i class="fa fa-clock-o"></i> Sessions</a></li>
             <li data-ng-show="access.viewEvents" data-ng-class="(path[2] == 'events' || path[2] == 'events-settings') && 'active'"><a href="#/realms/{{realm.realm}}/events"><i class="fa fa-calendar"></i> Events</a></li>
         </ul>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-group.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-group.html
new file mode 100755
index 0000000..5728c22
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-group.html
@@ -0,0 +1,13 @@
+<div data-ng-controller="GroupTabCtrl">
+    <h1>
+        {{group.name|capitalize}}
+        <i id="removeGroup" class="pficon pficon-delete clickable" data-ng-show="access.manageUsers" data-ng-click="removeGroup()"></i>
+    </h1>
+
+    <ul class="nav nav-tabs">
+        <li ng-class="{active: !path[4]}"><a href="#/realms/{{realm.realm}}/groups/{{group.id}}">Settings</a></li>
+        <li ng-class="{active: path[4] == 'attributes'}"><a href="#/realms/{{realm.realm}}/groups/{{group.id}}/attributes">Attributes</a></li>
+        <li ng-class="{active: path[4] == 'role-mappings'}" ><a href="#/realms/{{realm.realm}}/groups/{{group.id}}/role-mappings">Role Mappings</a></li>
+        <li ng-class="{active: path[4] == 'members'}"><a href="#/realms/{{realm.realm}}/groups/{{group.id}}/members">Members</a></li>
+    </ul>
+</div>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-group-list.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-group-list.html
new file mode 100755
index 0000000..390dce3
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-group-list.html
@@ -0,0 +1,11 @@
+<div data-ng-controller="GroupTabCtrl">
+    <h1>
+        <span>User Groups</span>
+    </h1>
+
+    <ul class="nav nav-tabs">
+        <li ng-class="{active: path[2] == 'groups'}"><a href="#/realms/{{realm.realm}}/groups">Groups</a></li>
+        <li ng-class="{active: path[2] == 'default-groups'}"><a href="#/realms/{{realm.realm}}/default-groups">Default Groups</a><kc-tooltip>Set of groups that new users will automatically join.</kc-tooltip>
+        </li>
+    </ul>
+</div>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-realm.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-realm.html
index 76f01ea..5d14503 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-realm.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-realm.html
@@ -13,6 +13,7 @@
         <li ng-class="{active: path[2] == 'theme-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/theme-settings">{{:: 'realm-tab-themes' | translate}}</a></li>
         <li ng-class="{active: path[2] == 'cache-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/cache-settings">{{:: 'realm-tab-cache' | translate}}</a></li>
         <li ng-class="{active: path[2] == 'token-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/token-settings">{{:: 'realm-tab-tokens' | translate}}</a></li>
+        <li ng-class="{active: path[2] == 'client-initial-access'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/client-initial-access">{{:: 'realm-tab-client-initial-access' | translate}}</a></li>
         <li ng-class="{active: path[2] == 'defense'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/defense/headers">{{:: 'realm-tab-security-defenses' | translate}}</a></li>
     </ul>
 </div>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-user.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-user.html
old mode 100644
new mode 100755
index edc3a66..7508c83
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-user.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-user.html
@@ -10,6 +10,7 @@
         <li ng-class="{active: path[4] == 'user-attributes'}"><a href="#/realms/{{realm.realm}}/users/{{user.id}}/user-attributes">Attributes</a></li>
         <li ng-class="{active: path[4] == 'user-credentials'}" data-ng-show="access.manageUsers"><a href="#/realms/{{realm.realm}}/users/{{user.id}}/user-credentials">Credentials</a></li>
         <li ng-class="{active: path[4] == 'role-mappings'}" ><a href="#/realms/{{realm.realm}}/users/{{user.id}}/role-mappings">Role Mappings</a></li>
+        <li ng-class="{active: path[4] == 'groups'}" ><a href="#/realms/{{realm.realm}}/users/{{user.id}}/groups">Groups</a></li>
         <li ng-class="{active: path[4] == 'consents'}"><a href="#/realms/{{realm.realm}}/users/{{user.id}}/consents">Consents</a></li>
         <li ng-class="{active: path[4] == 'sessions'}" ><a href="#/realms/{{realm.realm}}/users/{{user.id}}/sessions">Sessions</a></li>
         <li ng-class="{active: path[4] == 'federated-identity' || path[1] == 'federated-identity'}" data-ng-show="user.federatedIdentities != null"><a href="#/realms/{{realm.realm}}/users/{{user.id}}/federated-identity">Identity Provider Links</a></li>
diff --git a/forms/common-themes/src/main/resources/theme/base/login/login.ftl b/forms/common-themes/src/main/resources/theme/base/login/login.ftl
index 925f99c..5786807 100755
--- a/forms/common-themes/src/main/resources/theme/base/login/login.ftl
+++ b/forms/common-themes/src/main/resources/theme/base/login/login.ftl
@@ -13,7 +13,11 @@
                     </div>
 
                     <div class="${properties.kcInputWrapperClass!}">
-                        <input id="username" class="${properties.kcInputClass!}" name="username" value="${(login.username!'')?html}" type="text" autofocus />
+                        <#if usernameEditDisabled??>
+                            <input id="username" class="${properties.kcInputClass!}" name="username" value="${(login.username!'')?html}" type="text" disabled />
+                        <#else>
+                            <input id="username" class="${properties.kcInputClass!}" name="username" value="${(login.username!'')?html}" type="text" autofocus />
+                        </#if>
                     </div>
                 </div>
 
@@ -29,7 +33,7 @@
 
                 <div class="${properties.kcFormGroupClass!}">
                     <div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
-                        <#if realm.rememberMe>
+                        <#if realm.rememberMe && !usernameEditDisabled??>
                             <div class="checkbox">
                                 <label>
                                     <#if login.rememberMe??>
@@ -56,7 +60,7 @@
             </form>
         </#if>
     <#elseif section = "info" >
-        <#if realm.password && realm.registrationAllowed>
+        <#if realm.password && realm.registrationAllowed && !usernameEditDisabled??>
             <div id="kc-registration">
                 <span>${msg("noAccount")} <a href="${url.registrationUrl}">${msg("doRegister")}</a></span>
             </div>
diff --git a/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-confirm.ftl b/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-confirm.ftl
new file mode 100644
index 0000000..f77eb38
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-confirm.ftl
@@ -0,0 +1,21 @@
+<#import "template.ftl" as layout>
+<@layout.registrationLayout displayMessage=false; section>
+    <#if section = "title">
+        ${msg("confirmLinkIdpTitle")}
+    <#elseif section = "header">
+         ${msg("confirmLinkIdpTitle")}
+    <#elseif section = "form">
+        <div id="kc-error-message">
+            <p class="instruction">${message.summary}</p>
+        </div>
+
+        <form id="kc-register-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
+
+                <div id="kc-form-buttons" class="${properties.kcFormGroupClass!}">
+                    <button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="submitAction" id="updateProfile" value="updateProfile">${msg("confirmLinkIdpReviewProfile")}</button>
+                    <button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="submitAction" id="linkAccount" value="linkAccount">${msg("confirmLinkIdpContinue", idpAlias)}</button>
+                </div>
+
+        </form>
+    </#if>
+</@layout.registrationLayout>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-email.ftl b/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-email.ftl
new file mode 100644
index 0000000..0ba0686
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-email.ftl
@@ -0,0 +1,15 @@
+<#import "template.ftl" as layout>
+<@layout.registrationLayout; section>
+    <#if section = "title">
+        ${msg("emailLinkIdpTitle", idpAlias)}
+    <#elseif section = "header">
+        ${msg("emailLinkIdpTitle", idpAlias)}
+    <#elseif section = "form">
+        <p id="instruction1" class="instruction">
+            ${msg("emailLinkIdp1", idpAlias, brokerContext.username, realm.name)}
+        </p>
+        <p id="instruction2" class="instruction">
+            ${msg("emailLinkIdp2")} <a href="${url.firstBrokerLoginUrl}">${msg("doClickHere")}</a> ${msg("emailLinkIdp3")}
+        </p>
+    </#if>
+</@layout.registrationLayout>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/login/login-update-profile.ftl b/forms/common-themes/src/main/resources/theme/base/login/login-update-profile.ftl
index 584bea3..458884c 100755
--- a/forms/common-themes/src/main/resources/theme/base/login/login-update-profile.ftl
+++ b/forms/common-themes/src/main/resources/theme/base/login/login-update-profile.ftl
@@ -6,7 +6,7 @@
         ${msg("loginProfileTitle")}
     <#elseif section = "form">
         <form id="kc-update-profile-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
-            <#if realm.editUsernameAllowed>
+            <#if user.editUsernameAllowed>
                 <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('username',properties.kcFormGroupErrorClass!)}">
                     <div class="${properties.kcLabelWrapperClass!}">
                         <label for="username" class="${properties.kcLabelClass!}">${msg("username")}</label>
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties
index 3445de5..d968379 100644
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties
@@ -197,4 +197,5 @@ locale_de=Deutsch
 locale_en=English
 locale_it=Italian
 locale_pt-BR=Portugu\u00EAs (Brasil)
-locale_fr=Français
+locale_fr=Fran\u00e7ais
+locale_es=Espa\u00F1ol
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
index 6fa441c..6885308 100644
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
@@ -79,6 +79,11 @@ emailVerifyInstruction1=An email with instructions to verify your email address 
 emailVerifyInstruction2=Haven''t received a verification code in your email?
 emailVerifyInstruction3=to re-send the email.
 
+emailLinkIdpTitle=Link {0}
+emailLinkIdp1=An email with instructions to link {0} account {1} with your {2} account has been sent to you.
+emailLinkIdp2=Haven''t received a verification code in your email?
+emailLinkIdp3=to re-send the email.
+
 backToLogin=&laquo; Back to Login
 
 emailInstruction=Enter your username or email address and we will send you instructions on how to create a new password.
@@ -132,13 +137,19 @@ invalidTotpMessage=Invalid authenticator code.
 usernameExistsMessage=Username already exists.
 emailExistsMessage=Email already exists.
 
-federatedIdentityEmailExistsMessage=User with email already exists. Please login to account management to link the account.
-federatedIdentityUsernameExistsMessage=User with username already exists. Please login to account management to link the account.
+federatedIdentityExistsMessage=User with {0} {1} already exists. Please login to account management to link the account.
+
+confirmLinkIdpTitle=Account already exists
+federatedIdentityConfirmLinkMessage=User with {0} {1} already exists. How do you want to continue?
+federatedIdentityConfirmReauthenticateMessage=Authenticate as {0} to link your account with {1}
+confirmLinkIdpReviewProfile=Review profile info
+confirmLinkIdpContinue=Link {0} with existing account
 
 configureTotpMessage=You need to set up Mobile Authenticator to activate your account.
 updateProfileMessage=You need to update your user profile to activate your account.
 updatePasswordMessage=You need to change your password to activate your account.
 verifyEmailMessage=You need to verify your email address to activate your account.
+linkIdpMessage=You need to verify your email address to link your account with {0}.
 
 emailSentMessage=You should receive an email shortly with further instructions.
 emailSendErrorMessage=Failed to send email, please try again later.
@@ -174,13 +185,13 @@ resetCredentialNotAllowedMessage=Reset Credential not allowed
 
 permissionNotApprovedMessage=Permission not approved.
 noRelayStateInResponseMessage=No relay state in response from identity provider.
-identityProviderAlreadyLinkedMessage=The identity returned by the identity provider is already linked to another user.
 insufficientPermissionMessage=Insufficient permissions to link identities.
 couldNotProceedWithAuthenticationRequestMessage=Could not proceed with authentication request to identity provider.
 couldNotObtainTokenMessage=Could not obtain token from identity provider.
 unexpectedErrorRetrievingTokenMessage=Unexpected error when retrieving token from identity provider.
 unexpectedErrorHandlingResponseMessage=Unexpected error when handling response from identity provider.
 identityProviderAuthenticationFailedMessage=Authentication failed. Could not authenticate with identity provider.
+identityProviderDifferentUserMessage=Authenticated as {0}, but expected to be authenticated as {1}
 couldNotSendAuthenticationRequestMessage=Could not send authentication request to identity provider.
 unexpectedErrorHandlingRequestMessage=Unexpected error when handling authentication request to identity provider.
 invalidAccessCodeMessage=Invalid access code.
@@ -188,6 +199,7 @@ sessionNotActiveMessage=Session not active.
 invalidCodeMessage=An error occurred, please login again through your application.
 identityProviderUnexpectedErrorMessage=Unexpected error when authenticating with identity provider
 identityProviderNotFoundMessage=Could not find an identity provider with the identifier.
+identityProviderLinkSuccess=Your account was successfully linked with {0} account {1} .
 realmSupportsNoCredentialsMessage=Realm does not support any credential type.
 identityProviderNotUniqueMessage=Realm supports multiple identity providers. Could not determine which identity provider should be used to authenticate with.
 emailVerifiedMessage=Your email address has been verified.
@@ -196,7 +208,8 @@ locale_de=German
 locale_en=English
 locale_it=Italian
 locale_pt-BR=Portugu\u00EAs (Brasil)
-locale_fr=Français
+locale_fr=Fran\u00e7ais
+locale_es=Espa\u00F1ol
 
 backToApplication=&laquo; Back to Application
 missingParameterMessage=Missing parameters\: {0}
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_es.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_es.properties
new file mode 100644
index 0000000..ff43eed
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_es.properties
@@ -0,0 +1,205 @@
+doLogIn=Iniciar sesi\u00F3n
+doRegister=Reg\u00EDstrate
+doCancel=Cancelar
+doSubmit=Enviar
+doYes=S\u00ED
+doNo=No
+doContinue=Continuar
+doAccept=Aceptar
+doDecline=Declinar
+doForgotPassword=\u00BFHas olvidado tu contrase\u00F1a?
+doClickHere=Haz clic aqu\u00ED
+doImpersonate=Personificar
+kerberosNotConfigured=Kerberos no configurado
+kerberosNotConfiguredTitle=Kerberos no configurado
+bypassKerberosDetail=O bien no est\u00E1s identificado mediante Kerberos o tu navegador no est\u00E1 configurado para identificarse mediante Kerberos. Por favor haz clic para identificarte por otro medio.
+kerberosNotSetUp=Kerberos no est\u00E1 configurado. No puedes identificarte.
+registerWithTitle=Reg\u00EDstrate con {0}
+registerWithTitleHtml=Reg\u00EDstrate con <strong>{0}</strong>
+loginTitle=Inicia sesi\u00F3n en {0}
+loginTitleHtml=Inicia sesi\u00F3n en {0}
+impersonateTitle={0}\u00A0Personificar Usuario
+impersonateTitleHtml=<strong>{0}</strong> Personificar Usuario</strong>
+realmChoice=Dominio
+unknownUser=Usuario desconocido
+loginTotpTitle=Configura tu aplicaci\u00F3n de identificaci\u00F3n m\u00F3vil
+loginProfileTitle=Actualiza la informaci\u00F3n de tu cuenta
+loginTimeout=Has tardado demasiado en identificarte. Inicia de nuevo la identificaci\u00F3n.
+oauthGrantTitle=Concesi\u00F3n OAuth
+oauthGrantTitleHtml=Acceso temporal para <strong>{0}</strong> solicitado por
+errorTitle=Lo sentimos...
+errorTitleHtml=Lo <strong>sentimos</strong>...
+emailVerifyTitle=Verificaci\u00F3n del email
+emailForgotTitle=\u00BFHas olvidado tu contrase\u00F1a?
+updatePasswordTitle=Modificaci\u00F3n de contrase\u00F1a
+codeSuccessTitle=C\u00F3digo de \u00E9xito
+codeErrorTitle=C\u00F3digo de error: {0}
+
+termsTitle=T\u00E9rminos y Condiciones
+termsTitleHtml=T\u00E9rminos y Condiciones
+termsText=<p>T\u00E9rmines y condiciones a definir</p>
+
+recaptchaFailed=Reconocimiento de texto inv\u00E1lido
+recaptchaNotConfigured=El reconocimiento de texto es obligatorio pero no est\u00E1 configurado
+consentDenied=Consentimiento rechazado.
+
+noAccount=\u00BFUsuario nuevo?
+username=Usuario
+usernameOrEmail=Usuario o email
+firstName=Nombre
+givenName=Nombre de pila
+fullName=Nombre completo
+lastName=Apellidos
+familyName=Apellidos
+email=Email
+password=Contrase\u00F1a
+passwordConfirm=Confirma la contrase\u00F1a
+passwordNew=Nueva contrase\u00F1a
+passwordNewConfirm=Confirma la nueva contrase\u00F1a
+rememberMe=Seguir conectado
+authenticatorCode=C\u00F3digo de identificaci\u00F3n
+address=Direcci\u00F3n
+street=Calle
+locality=Ciudad o Municipio
+region=Estado, Provincia, o Regi\u00F3n
+postal_code=C\u00F3digo Postal
+country=Pa\u00EDs
+emailVerified=Email verificado
+gssDelegationCredential=GSS Delegation Credential
+
+loginTotpStep1=Instala <a href=\"https://fedorahosted.org/freeotp/\" target=\"_blank\">FreeOTP</a> o Google Authenticator en tu tel\u00E9fono m\u00F3vil. Ambas aplicaciones est\u00E1n disponibles en <a href=\"https://play.google.com\">Google Play</a> y en la App Store de Apple.
+loginTotpStep2=Abre la aplicacvi\u00F3n y escanea el c\u00F3digo o introduce la clave.
+loginTotpStep3=Introduce el c\u00F3digo \u00FAnico que te muestra la aplicaci\u00F3n de autenticaci\u00F3n y haz clic en Enviar para finalizar la configuraci\u00F3n
+loginTotpOneTime=C\u00F3digo de un solo uso
+
+oauthGrantRequest=\u00BFQuieres permitir estos privilegios de acceso?
+inResource=en
+
+emailVerifyInstruction1=Te hemos enviado un email con instrucciones para verificar tu email.
+emailVerifyInstruction2=\u00BFNo has recibido un c\u00F3digo de verificaci\u00F3n en tu email?
+emailVerifyInstruction3=para reenviar el email.
+
+backToLogin=&laquo; Volver a la identificaci\u00F3n
+
+emailInstruction=Indica tu usuario o email y te enviaremos instruciones indicando como generar una nueva contrase\u00F1a.
+
+copyCodeInstruction=Por favor, copia y pega este c\u00F3digo en tu aplicaci\u00F3n:
+
+personalInfo=Informaci\u00F3n personal:
+role_admin=Admin
+role_realm-admin=Administrador del dominio
+role_create-realm=Crear dominio
+role_create-client=Crear cliente
+role_view-realm=Ver dominio
+role_view-users=Ver usuarios
+role_view-applications=Ver aplicaciones
+role_view-clients=Ver clientes
+role_view-events=Ver eventos
+role_view-identity-providers=Ver proveedores de identidad
+role_manage-realm=Gestionar dominio
+role_manage-users=Gestionar usuarios
+role_manage-applications=Gestionar aplicaciones
+role_manage-identity-providers=Gestionar proveedores de identidad
+role_manage-clients=Gestionar clientes
+role_manage-events=Gestionar eventos
+role_view-profile=Ver perfil
+role_manage-account=Gestionar cuenta
+role_read-token=Leer token
+role_offline-access=Acceso sin conexi\u00F3n
+client_account=Cuenta
+client_security-admin-console=Consola de Administraci\u00F3n de Seguridad
+client_realm-management=Gesti\u00F3n del dominio
+client_broker=Broker
+
+invalidUserMessage=Usuario o contrase\u00F1a incorrectos.
+invalidEmailMessage=Email no v\u00E1lido
+accountDisabledMessage=La cuenta est\u00E1 desactivada, contacta con el administrador.
+accountTemporarilyDisabledMessage=La cuenta est\u00E1 temporalmente desactivada, contacta con el administrador o int\u00E9ntalo de nuevo m\u00E1s tarde.
+expiredCodeMessage=Se agot\u00F3 el tiempo m\u00E1ximo para la identificaci\u00F3n. Por favor identificate de nuevo.
+
+missingFirstNameMessage=Por favor indica tu nombre.
+missingLastNameMessage=Por favor indica tus apellidos.
+missingEmailMessage=Por favor indica tu email.
+missingUsernameMessage=Por favor indica tu usuario.
+missingPasswordMessage=Por favor indica tu contrase\u00F1a.
+missingTotpMessage=Por favor indica tu c\u00F3digo de autenticaci\u00F3n
+notMatchPasswordMessage=Las contrase\u00F1as no coinciden.
+
+invalidPasswordExistingMessage=La contrase\u00F1a actual no es correcta.
+invalidPasswordConfirmMessage=La confirmaci\u00F3n de contrase\u00F1a no coincide.
+invalidTotpMessage=El c\u00F3digo de autenticaci\u00F3n no es v\u00E1lido.
+
+usernameExistsMessage=El nombre de usuario ya existe
+emailExistsMessage=El email ya existe
+
+federatedIdentityEmailExistsMessage=Ya existe un usuario con este email. Por favor accede a la gesti\u00F3n de tu cuenta para enlazarlo.
+federatedIdentityUsernameExistsMessage=Ya existe un usuario con este nombre de usuario. Por favor accede a la gesti\u00F3n de tu cuenta para enlazarlo.
+
+configureTotpMessage=Tienes que configurar la aplicaci\u00F3n m\u00F3vil de identificaci\u00F3n para activar tu cuenta.
+updateProfileMessage=Tienes que actualizar tu perfil de usuario para activar tu cuenta.
+updatePasswordMessage=Tienes que cambiar tu contrase\u00F1a para activar tu cuenta.
+verifyEmailMessage=Tienes que verificar tu email para activar tu cuenta.
+
+emailSentMessage=En breve deber\u00EDas recibir un mensaje con m\u00E1s instrucciones
+emailSendErrorMessage=Fall\u00F3 el env\u00EDo del email, por favor int\u00E9ntalo de nuevo m\u00E1s tarde.
+
+accountUpdatedMessage=Tu cuenta se ha actualizado.
+accountPasswordUpdatedMessage=Tu contrase\u00F1a se ha actualizado.
+
+noAccessMessage=Sin acceso
+
+invalidPasswordMinLengthMessage=Contrase\u00F1a incorrecta: longitud m\u00EDnima {0}.
+invalidPasswordMinDigitsMessage=Contrase\u00F1a incorrecta: debe contaner al menos {0} caracteres num\u00E9ricos.
+invalidPasswordMinLowerCaseCharsMessage=Contrase\u00F1a incorrecta: debe contener al menos {0} letras min\u00FAsculas.
+invalidPasswordMinUpperCaseCharsMessage=Contrase\u00F1a incorrecta: debe contener al menos {0} letras may\u00FAsculas.
+invalidPasswordMinSpecialCharsMessage=Contrase\u00F1a incorrecta: debe contener al menos {0} caracteres especiales.
+invalidPasswordNotUsernameMessage=Contrase\u00F1a incorrecta: no puede ser igual al nombre de usuario.
+invalidPasswordRegexPatternMessage=Contrase\u00F1a incorrecta: no cumple la expresi\u00F3n regular.
+invalidPasswordHistoryMessage=Contrase\u00F1a incorrecta: no puede ser igual a ninguna de las \u00FAltimas {0} contrase\u00F1as.
+
+failedToProcessResponseMessage=Fallo al procesar la respuesta
+httpsRequiredMessage=HTTPS obligatorio
+realmNotEnabledMessage=El dominio no est\u00E1 activado
+invalidRequestMessage=Petici\u00F3n incorrecta
+failedLogout=Fall\u00F3 la desconexi\u00F3n.
+unknownLoginRequesterMessage=Solicitante de identificaci\u00F3n desconocido
+loginRequesterNotEnabledMessage=El solicitante de inicio de sesi\u00F3n est\u00E1 desactivado
+bearerOnlyMessage=Las aplicaciones Bearer-only no pueden iniciar sesi\u00F3n desde el navegador.
+directGrantsOnlyMessage=Los clientes de tipo Direct-grants-only no pueden iniciar sesi\u00F3n desde el navegador.
+invalidRedirectUriMessage=La URI de redirecci\u00F3n no es correcta
+unsupportedNameIdFormatMessage=NameIDFormat no soportado
+invlidRequesterMessage=Solicitante no v\u00E1lido
+registrationNotAllowedMessage=El registro no est\u00E1 permitido
+resetCredentialNotAllowedMessage=El renicio de las credenciales no est\u00E1 permitido
+
+permissionNotApprovedMessage=Permiso no aprobado.
+noRelayStateInResponseMessage=Sin estado de retransmisi\u00F3n en la respuesta del proveedor de identidad.
+identityProviderAlreadyLinkedMessage=La identidad devuelta por el proveedor de identidad ya est\u00E1 asociada a otro usuario.
+insufficientPermissionMessage=Permisos insuficientes para enlazar identidades.
+couldNotProceedWithAuthenticationRequestMessage=No se pudo continuar con la petici\u00F3n de autenticaci\u00F3n al proveedor de identidad.
+couldNotObtainTokenMessage=.No se pudo obtener el c\u00F3digo del proveedor de identidad
+unexpectedErrorRetrievingTokenMessage=Error inesperado obteniendo el token del proveedor de identidad
+unexpectedErrorHandlingResponseMessage=Error inesperado procesando la respuesta del proveedor de identidad.
+identityProviderAuthenticationFailedMessage=Fall\u00F3 la autenticaci\u00F3n. No fue posible autenticarse en el proveedor de identidad.
+couldNotSendAuthenticationRequestMessage=No se pudo enviar la petici\u00F3n de identificaci\u00F3n al proveedor de identidad.
+unexpectedErrorHandlingRequestMessage=Error inesperado durante la petici\u00F3n de identificaci\u00F3n al proveedor de identidad.
+invalidAccessCodeMessage=C\u00F3digo de acceso no v\u00E1lido.
+sessionNotActiveMessage=La sesi\u00F3n no est\u00E1 activa
+invalidCodeMessage=Ha ocurrido un error, por favor identificate de nuevo desde tu aplicaci\u00F3n.
+identityProviderUnexpectedErrorMessage=Error no esperado intentado autenticar en el proveedor de identidad.
+identityProviderNotFoundMessage=No se encontr\u00F3 un proveedor de identidad.
+realmSupportsNoCredentialsMessage=El dominio no soporta ning\u00FAn tipo de credenciales.
+identityProviderNotUniqueMessage=El dominio soporta m\u00FAltiples proveedores de identidad. No se pudo determinar el proveedor de identidad que deber\u00EDa ser utilizado para identificarse.
+emailVerifiedMessage=Tu email ha sido verificado.
+
+locale_de=German
+locale_en=English
+locale_it=Italian
+locale_pt-BR=Portugu\u00EAs (Brasil)
+locale_fr=Fran\u00C3\u00A7ais
+locale_es=Espa\u00F1ol
+
+backToApplication=&laquo; Volver a la aplicaci\u00F3n
+missingParameterMessage=Par\u00E1metros que faltan: {0}
+clientNotFoundMessage=Cliente no encontrado
+invalidParameterMessage=Par\u00E1metro no v\u00E1lido: {0}
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_fr.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_fr.properties
index 22bd4ae..495dde6 100644
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_fr.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_fr.properties
@@ -6,48 +6,48 @@ doYes=Oui
 doNo=Non
 doContinue=Continuer
 doAccept=Accepter
-doDecline=Decliner
-doForgotPassword=Mot de passe oublié ?
+doDecline=D\u00e9cliner
+doForgotPassword=Mot de passe oubli\u00e9 ?
 doClickHere=Cliquez ici
 doImpersonate=Impersonate
-kerberosNotConfigured=Kerberos non configuré
-kerberosNotConfiguredTitle=Kerberos non configuré
-bypassKerberosDetail=Si vous n''êtes pas connecté via Kerberos ou bie que votre navigateur n''est opas configurer pour la connexion via Kerberos.  Veuillez cliquer pour vous connecter via un autre moyen.
-kerberosNotSetUp=Kerberos n''est pas configuré. Connexion impossible.
+kerberosNotConfigured=Kerberos non configur\u00e9
+kerberosNotConfiguredTitle=Kerberos non configur\u00e9
+bypassKerberosDetail=Si vous n''\u00eates pas connect\u00e9 via Kerberos ou bien que votre navigateur n''est pas configur\u00e9 pour la connexion via Kerberos.  Veuillez cliquer pour vous connecter via un autre moyen.
+kerberosNotSetUp=Kerberos n''est pas configur\u00e9. Connexion impossible.
 registerWithTitle=Enregistrement avec {0}
-registerWithTitleHtml=Enregistrement avec<strong>{0}</strong>
-loginTitle=Connecté {0}
-loginTitleHtml=Connecté <strong>{0}</strong>
+registerWithTitleHtml=Enregistrement avec <strong>{0}</strong>
+loginTitle=Se connecter \u00e0 {0}
+loginTitleHtml=Se connecter \u00e0 <strong>{0}</strong>
 impersonateTitle={0} utilisateur impersonate
 impersonateTitleHtml=<strong>{0}</strong> utilisateur impersonate</strong>
 realmChoice=Domaine
 unknownUser=Utilisateur inconnu
 loginTotpTitle=Configuration de l''authentification par mobile
-loginProfileTitle=Mise à jour du compte
-loginTimeout=Le temps imparti pour la connexion est écoulé. Le processus de connexion redémarre depuis le debut.
+loginProfileTitle=Mise \u00e0 jour du compte
+loginTimeout=Le temps imparti pour la connexion est \u00e9coul\u00e9. Le processus de connexion red\u00e9marre depuis le d\u00e9but.
 oauthGrantTitle=OAuth Grant
-oauthGrantTitleHtml=Accès temporaire pour <strong>{0}</strong> demandé par
-errorTitle=Nous sommes désolé ...
-errorTitleHtml=Nous sommes <strong>désolé</strong> ...
-emailVerifyTitle=Vérification du courriel
-emailForgotTitle=Mot de passe oublié ?
-updatePasswordTitle=Mise à jour du mot de passe
-codeSuccessTitle=Code succès
-codeErrorTitle=Code Erreur\: {0}
+oauthGrantTitleHtml=Acc\u00e8s temporaire pour <strong>{0}</strong> demand\u00e9 par
+errorTitle=Nous sommes d\u00e9sol\u00e9 ...
+errorTitleHtml=Nous sommes <strong>d\u00e9sol\u00e9</strong> ...
+emailVerifyTitle=V\u00e9rification du courriel
+emailForgotTitle=Mot de passe oubli\u00e9 ?
+updatePasswordTitle=Mise \u00e0 jour du mot de passe
+codeSuccessTitle=Code succ\u00e8s
+codeErrorTitle=Code d''erreur\: {0}
 
 termsTitle=Termes et Conditions
 termsTitleHtml=Termes et Conditions
-termsText=<p>Termes et conditions à définir</p>
+termsText=<p>Termes et conditions \u00e0 d\u00e9finir</p>
 
 recaptchaFailed=Re-captcha invalide
-recaptchaNotConfigured=Re-captcha est requis, mais il n''est pas configuré
-consentDenied=Consentement refusé.
+recaptchaNotConfigured=Re-captcha est requis, mais il n''est pas configur\u00e9
+consentDenied=Consentement refus\u00e9.
 
 noAccount=Nouvel utilisateur?
 username=Nom d''utilisateur
 usernameOrEmail=Nom d''utilisateur ou courriel
-firstName=Prénom
-givenName=Prénom
+firstName=Pr\u00e9nom
+givenName=Pr\u00e9nom
 fullName=Nom complet
 lastName=Nom
 familyName=Nom de famille
@@ -55,38 +55,38 @@ email=Courriel
 password=Mot de passe
 passwordConfirm=Confirmation du mot de passe
 passwordNew=Nouveau mot de passe
-passwordNewConfirm=Confirmation du nouveau not de passe
+passwordNewConfirm=Confirmation du nouveau mot de passe
 rememberMe=Se souvenir de moi
-authenticatorCode=Code à usage unique
+authenticatorCode=Code \u00e0 usage unique
 address=Adresse
 street=Rue
-locality=Ville ou Localité
-region=État, Province, ou Région
+locality=Ville ou Localit\u00e9
+region=\u00c9tat, Province ou R\u00e9gion
 postal_code=Code postal
 country=Pays
-emailVerified=Courriel vérifié
-gssDelegationCredential=GSS Delegation Credential
+emailVerified=Courriel v\u00e9rifi\u00e9
+gssDelegationCredential=Accr\u00e9ditation de d\u00e9l\u00e9gation GSS
 
 loginTotpStep1=Installez <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> ou bien Google Authenticator sur votre mobile. Ces deux applications sont disponibles sur <a href="https://play.google.com">Google Play</a> et Apple App Store.
-loginTotpStep2=Ouvrez l''application et scanner le code bar ou entrez la clef.
+loginTotpStep2=Ouvrez l''application et scanner le code barre ou entrez la cl\u00e9.
 loginTotpStep3=Entrez le code \u00e0 usage unique fourni par l''application et cliquez sur Sauvegarder pour terminer.
-loginTotpOneTime=Code à usage unique
+loginTotpOneTime=Code \u00e0 usage unique
 
-oauthGrantRequest=Voulez-vous accorder ces privileges d''accès ?
+oauthGrantRequest=Voulez-vous accorder ces privil\u00e8ges d''acc\u00e8s ?
 inResource=dans
 
-emailVerifyInstruction1=Un courriel avec des instructions à suivre vous a été envoyé.
-emailVerifyInstruction2=Vous n''avez pas reçu de code dans le courriel ?
+emailVerifyInstruction1=Un courriel avec des instructions \u00e0 suivre vous a \u00e9t\u00e9 envoy\u00e9.
+emailVerifyInstruction2=Vous n''avez pas re\u00e7u de code dans le courriel?
 emailVerifyInstruction3=Pour renvoyer le courriel.
 
-backToLogin=&laquo; Retour à la connexion
+backToLogin=&laquo; Retour \u00e0 la connexion
 
-emailInstruction=Entrez votre nom d''utilisateur ou votre courriel, un email va vous \u00eatre envoyer vous permettant de créer un nouveau mot de passe.
+emailInstruction=Entrez votre nom d''utilisateur ou votre courriel, un courriel va vous \u00eatre envoy\u00e9 vous permettant de cr\u00e9er un nouveau mot de passe.
 
-copyCodeInstruction=Copiez le code et recopiez le dans votre application :
+copyCodeInstruction=Copiez le code et recopiez le dans votre application:
 
 personalInfo=Information personnelle:
-role_admin=Adminitrateur
+role_admin=Administrateur
 role_realm-admin=Administrateur du domaine
 role_create-realm=Cr\u00e9er un domaine
 role_view-realm=Voir un domaine
@@ -99,8 +99,8 @@ role_manage-realm=G\u00e9rer le domaine
 role_manage-users=G\u00e9rer les utilisateurs
 role_manage-applications=G\u00e9rer les applications
 role_manage-identity-providers=G\u00e9rer les fournisseurs d''identit\u00e9s
-role_manage-clients=G\u00e9rer les  clients
-role_manage-events=G\u00e9rer les  \u00e9v\u00e9nements
+role_manage-clients=G\u00e9rer les clients
+role_manage-events=G\u00e9rer les \u00e9v\u00e9nements
 role_view-profile=Voir le profile
 role_manage-account=G\u00e9rer le compte
 role_read-token=Lire le jeton d''authentification
@@ -111,9 +111,9 @@ client_realm-management=Gestion du domaine
 client_broker=Broker
 
 invalidUserMessage=Nom d''utilisateur ou mot de passe invalide.
-invalidEmailMessage=Adresse courriel invalide.
-accountDisabledMessage=Compte désactivé, contactez votre administrateur.
-accountTemporarilyDisabledMessage=Ce compte est temporairement désactivé, contactez votre administrateur ou bien réassayer plus tard.
+invalidEmailMessage=Courriel invalide.
+accountDisabledMessage=Compte d\u00e9sactiv\u00e9, contactez votre administrateur.
+accountTemporarilyDisabledMessage=Ce compte est temporairement d\u00e9sactiv\u00e9, contactez votre administrateur ou bien r\u00e9assayer plus tard.
 expiredCodeMessage=Fin de connexion. Veuillez vous reconnecter.
 
 missingFirstNameMessage=Veuillez entrer votre pr\u00e9nom.
@@ -131,75 +131,76 @@ invalidTotpMessage=Le code d''authentification est invalide.
 usernameExistsMessage=Le nom d''utilisateur existe d\u00e9j\u00e0.
 emailExistsMessage=Le courriel existe d\u00e9j\u00e0.
 
-federatedIdentityEmailExistsMessage=Cet utilisateur avec ce courriel existe déjà. Veuillez vous connecté au gestionnaire de compte pour lier le compte.
-federatedIdentityUsernameExistsMessage=Cet utilisateur avec ce nom d''utilisateur existe déjà. Veuillez vous connecté au gestionnaire de compte pour lier le compte.
+federatedIdentityEmailExistsMessage=Cet utilisateur avec ce courriel existe d\u00e9j\u00e0. Veuillez vous connect\u00e9 au gestionnaire de compte pour lier le compte.
+federatedIdentityUsernameExistsMessage=Cet utilisateur avec ce nom d''utilisateur existe d\u00e9j\u00e0. Veuillez vous connect\u00e9 au gestionnaire de compte pour lier le compte.
 
 configureTotpMessage=Vous devez configurer l''authentification par mobile pour activer votre compte.
-updateProfileMessage=Vous devez mettre à jour votre profile pour activer votre compte.
+updateProfileMessage=Vous devez mettre \u00e0 jour votre profile pour activer votre compte.
 updatePasswordMessage=Vous devez changer votre mot de passe pour activer votre compte.
-verifyEmailMessage=Vous devez vérifier votre courriel pour activer votre compte.
+verifyEmailMessage=Vous devez v\u00e9rifier votre courriel pour activer votre compte.
 
 emailSentMessage=Vous devriez recevoir rapidement un courriel avec de plus ample instructions.
 emailSendErrorMessage=Erreur lors de l''envoie du courriel, veuillez essayer plus tard.
 
-accountUpdatedMessage=Votre compte a été mis à jour.
-accountPasswordUpdatedMessage=Votre mot de passe a été mis à jour.
+accountUpdatedMessage=Votre compte a \u00e9t\u00e9 mis \u00e0 jour.
+accountPasswordUpdatedMessage=Votre mot de passe a \u00e9t\u00e9 mis \u00e0 jour.
 
-noAccessMessage=Aucun accès
+noAccessMessage=Aucun acc\u00e8s
 
-invalidPasswordMinLengthMessage=Mot de passe invalide : longueur minimale {0}.
-invalidPasswordMinDigitsMessage=Mot de passe invalide : doit contenir au moins {0} chiffre(s).
-invalidPasswordMinLowerCaseCharsMessage=Mot de passe invalide : doit contenir au moins {0} lettre(s) en minuscule.
-invalidPasswordMinUpperCaseCharsMessage=Mot de passe invalide : doit contenir au moins {0} lettre(s) en majuscule.
-invalidPasswordMinSpecialCharsMessage=Mot de passe invalide : doit contenir au moins {0} caract\u00e8re(s) sp\u00e9ciaux.
-invalidPasswordNotUsernameMessage=Mot de passe invalide : ne doit pas etre identique au nom d''utilisateur.
-invalidPasswordRegexPatternMessage=Mot de passe invalide : ne valide pas l''expression rationnelle.
-invalidPasswordHistoryMessage=Mot de passe invalide : ne doit pas \u00eatre \u00e9gal aux {0} derniers mot de passe.
+invalidPasswordMinLengthMessage=Mot de passe invalide: longueur minimale {0}.
+invalidPasswordMinDigitsMessage=Mot de passe invalide: doit contenir au moins {0} chiffre(s).
+invalidPasswordMinLowerCaseCharsMessage=Mot de passe invalide: doit contenir au moins {0} lettre(s) en minuscule.
+invalidPasswordMinUpperCaseCharsMessage=Mot de passe invalide: doit contenir au moins {0} lettre(s) en majuscule.
+invalidPasswordMinSpecialCharsMessage=Mot de passe invalide: doit contenir au moins {0} caract\u00e8re(s) sp\u00e9ciaux.
+invalidPasswordNotUsernameMessage=Mot de passe invalide: ne doit pas \u00eatre identique au nom d''utilisateur.
+invalidPasswordRegexPatternMessage=Mot de passe invalide: ne valide pas l''expression rationnelle.
+invalidPasswordHistoryMessage=Mot de passe invalide: ne doit pas \u00eatre \u00e9gal aux {0} derniers mot de passe.
 
 
-failedToProcessResponseMessage=Erreur lors du traitement de la réponse
+failedToProcessResponseMessage=Erreur lors du traitement de la r\u00e9ponse
 httpsRequiredMessage=Le protocole HTTPS est requis
-realmNotEnabledMessage=Le domaine n''est pas activé
-invalidRequestMessage=Requete invalide
-failedLogout=La déconnexion a échouée
+realmNotEnabledMessage=Le domaine n''est pas activ\u00e9
+invalidRequestMessage=Requ\u00eate invalide
+failedLogout=La d\u00e9connexion a \u00e9chou\u00e9e
 unknownLoginRequesterMessage=Compte inconnu du demandeur
 loginRequesterNotEnabledMessage=La connexion du demandeur n''est pas active
-bearerOnlyMessage=Les applications Bearer-only ne sont pas autorisées à initier la connexion par navigateur.
-directGrantsOnlyMessage=Les clients Direct-grants-only ne sont pas autorisés à initier la connexion par navigateur.
+bearerOnlyMessage=Les applications Bearer-only ne sont pas autoris\u00e9es \u00e0 initier la connexion par navigateur.
+directGrantsOnlyMessage=Les clients Direct-grants-only ne sont pas autoris\u00e9s \u00e0 initier la connexion par navigateur.
 invalidRedirectUriMessage=L''uri de redirection est invalide
-unsupportedNameIdFormatMessage=NameIDFormat non supporté
+unsupportedNameIdFormatMessage=NameIDFormat non support\u00e9
 invlidRequesterMessage=Demandeur invalide
-registrationNotAllowedMessage=L''enregistrement n''est pas autorisé
-resetCredentialNotAllowedMessage=La remise \u00e0 z\u00e9ro n''est pas autorisé
-
-permissionNotApprovedMessage=La permission n''est pas approuvée.
-noRelayStateInResponseMessage=Aucun état de relais dans la réponse du fournisseur d''identité.
-identityProviderAlreadyLinkedMessage=L''identité retournée par le fournisseur d''identité est déjà liée avec un autre utilisateur.
-insufficientPermissionMessage=Permissions insuffisantes pour lier les identités.
-couldNotProceedWithAuthenticationRequestMessage=Impossible de continuer avec la requête d''authentification vers le fournisseur d''identité.
-couldNotObtainTokenMessage=Impossible de récuperer le jeton du fournisseur d''identité.
-unexpectedErrorRetrievingTokenMessage=Erreur inattendue lors de la récupération du jeton provenant du fournisseur d''identité.
-unexpectedErrorHandlingResponseMessage=Erreur inattendue lors du traitement de la réponse provenant du fournisseur d''identité.
-identityProviderAuthenticationFailedMessage=L''authentification a échouée. Impossible de s''authentifier avec le fournisseur d''identité.
-couldNotSendAuthenticationRequestMessage=Impossible d''envoyer la requête d''authentification vers le fournisseur d''identité.
-unexpectedErrorHandlingRequestMessage=Erreur inattendue lors du traitement de la requête vers le fournisseur d''identité.
-invalidAccessCodeMessage=Code d''accès invalide.
+registrationNotAllowedMessage=L''enregistrement n''est pas autoris\u00e9
+resetCredentialNotAllowedMessage=La remise \u00e0 z\u00e9ro n''est pas autoris\u00e9
+
+permissionNotApprovedMessage=La permission n''est pas approuv\u00e9e.
+noRelayStateInResponseMessage=Aucun \u00e9tat de relais dans la r\u00e9ponse du fournisseur d''identit\u00e9.
+identityProviderAlreadyLinkedMessage=L''identit\u00e9 retourn\u00e9e par le fournisseur d''identit\u00e9 est d\u00e9j\u00e0 li\u00e9e avec un autre utilisateur.
+insufficientPermissionMessage=Permissions insuffisantes pour lier les identit\u00e9s.
+couldNotProceedWithAuthenticationRequestMessage=Impossible de continuer avec la requ\u00eate d''authentification vers le fournisseur d''identit\u00e9.
+couldNotObtainTokenMessage=Impossible de r\u00e9cup\u00e9rer le jeton du fournisseur d''identit\u00e9.
+unexpectedErrorRetrievingTokenMessage=Erreur inattendue lors de la r\u00e9cup\u00e9ration du jeton provenant du fournisseur d''identit\u00e9.
+unexpectedErrorHandlingResponseMessage=Erreur inattendue lors du traitement de la r\u00e9ponse provenant du fournisseur d''identit\u00e9.
+identityProviderAuthenticationFailedMessage=L''authentification a \u00e9chou\u00e9e. Impossible de s''authentifier avec le fournisseur d''identit\u00e9.
+couldNotSendAuthenticationRequestMessage=Impossible d''envoyer la requ\u00eate d''authentification vers le fournisseur d''identit\u00e9.
+unexpectedErrorHandlingRequestMessage=Erreur inattendue lors du traitement de la requ\u00eate vers le fournisseur d''identit\u00e9.
+invalidAccessCodeMessage=Code d''acc\u00e8s invalide.
 sessionNotActiveMessage=La session n''est pas active.
-invalidCodeMessage=Une erreur est survenue, veuillez vous reconnecter à votre application.
-identityProviderUnexpectedErrorMessage=Erreur inattendue lors de l''authentification avec fournisseur d''identité.
-identityProviderNotFoundMessage=Impossible de trouver le fournisseur d''identité avec ce identifiant.
-realmSupportsNoCredentialsMessage=Ce domaine ne supporte aucun type d''accréditation.
-identityProviderNotUniqueMessage=Ce domaine autorise plusieurs fournisseurs d''identité. Impossible de déterminer fournisseurs d''identité avec lequel s''authentifier.
-emailVerifiedMessage=Votre adresse courriel a été vérifiée.
+invalidCodeMessage=Une erreur est survenue, veuillez vous reconnecter \u00e0 votre application.
+identityProviderUnexpectedErrorMessage=Erreur inattendue lors de l''authentification avec fournisseur d''identit\u00e9.
+identityProviderNotFoundMessage=Impossible de trouver le fournisseur d''identit\u00e9 avec cet identifiant.
+realmSupportsNoCredentialsMessage=Ce domaine ne supporte aucun type d''accr\u00e9ditation.
+identityProviderNotUniqueMessage=Ce domaine autorise plusieurs fournisseurs d''identit\u00e9. Impossible de d\u00e9terminer le fournisseur d''identit\u00e9 avec lequel s''authentifier.
+emailVerifiedMessage=Votre courriel a \u00e9t\u00e9 v\u00e9rifi\u00e9.
 
 locale_de=German
 locale_en=English
 locale_it=Italian
 locale_pt-BR=Portugu\u00EAs (Brasil)
-locale_fr=Français
+locale_fr=Fran\u00e7ais
+locale_es=Espa\u00F1ol
 
 
-backToApplication=&laquo; Revenir à l''application
-missingParameterMessage=Paramètres manquants \: {0}
+backToApplication=&laquo; Revenir \u00e0 l''application
+missingParameterMessage=Param\u00e8tres manquants\: {0}
 clientNotFoundMessage=Client inconnu.
-invalidParameterMessage=Paramètres invalide \: {0}
+invalidParameterMessage=Param\u00e8tre invalide\: {0}
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_it.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_it.properties
index e353153..026c7a4 100644
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_it.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_it.properties
@@ -189,7 +189,8 @@ locale_de=German
 locale_en=English
 locale_it=Italian
 locale_pt-BR=Portugu\u00EAs (Brasil)
-locale_fr=Français
+locale_fr=Fran\u00e7ais
+locale_es=Espa\u00F1ol
 
 backToApplication=&laquo; Torna all''Applicazione
 missingParameterMessage=Parametri Mancanti\: {0}
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties
index e2a2257..7c44eeb 100644
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties
@@ -194,7 +194,8 @@ locale_de=Deutsch
 locale_en=English
 locale_it=Italian
 locale_pt-BR=Portugu\u00EAs (BR)
-locale_fr=Français
+locale_fr=Fran\u00e7ais
+locale_es=Espa\u00F1ol
 
 backToApplication=&laquo; Voltar para o aplicativo
 missingParameterMessage=Par\u00E2metros que faltam\: {0}
diff --git a/forms/common-themes/src/main/resources/theme/keycloak/admin/resources/css/styles.css b/forms/common-themes/src/main/resources/theme/keycloak/admin/resources/css/styles.css
index c0e8fb2..500ed89 100644
--- a/forms/common-themes/src/main/resources/theme/keycloak/admin/resources/css/styles.css
+++ b/forms/common-themes/src/main/resources/theme/keycloak/admin/resources/css/styles.css
@@ -22,6 +22,11 @@ table {
     margin-top: 20px;
 }
 
+.no-margin-top {
+    margin-top: 0px !important;
+}
+
+
 
 /*********** Loading ***********/
 
diff --git a/forms/common-themes/src/main/resources/theme/keycloak/admin/theme.properties b/forms/common-themes/src/main/resources/theme/keycloak/admin/theme.properties
old mode 100644
new mode 100755
index f85279f..c930785
--- a/forms/common-themes/src/main/resources/theme/keycloak/admin/theme.properties
+++ b/forms/common-themes/src/main/resources/theme/keycloak/admin/theme.properties
@@ -1,3 +1,3 @@
 parent=base
 import=common/keycloak
-styles=lib/patternfly/css/patternfly.css lib/select2-3.4.1/select2.css css/styles.css
\ No newline at end of file
+styles=lib/patternfly/css/patternfly.css lib/select2-3.4.1/select2.css css/styles.css lib/angular/treeview/css/angular.treeview.css
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/keycloak/common/resources/lib/angular/treeview/angular.treeview.js b/forms/common-themes/src/main/resources/theme/keycloak/common/resources/lib/angular/treeview/angular.treeview.js
new file mode 100755
index 0000000..0e6512a
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/keycloak/common/resources/lib/angular/treeview/angular.treeview.js
@@ -0,0 +1,97 @@
+/*
+	@license Angular Treeview version 0.1.6
+	ⓒ 2013 AHN JAE-HA http://github.com/eu81273/angular.treeview
+	License: MIT
+
+
+	[TREE attribute]
+	angular-treeview: the treeview directive
+	tree-id : each tree's unique id.
+	tree-model : the tree model on $scope.
+	node-id : each node's id
+	node-label : each node's label
+	node-children: each node's children
+
+	<div
+		data-angular-treeview="true"
+		data-tree-id="tree"
+		data-tree-model="roleList"
+		data-node-id="roleId"
+		data-node-label="roleName"
+		data-node-children="children" >
+	</div>
+*/
+
+(function ( angular ) {
+	'use strict';
+
+	angular.module( 'angularTreeview', [] ).directive( 'treeModel', ['$compile', function( $compile ) {
+		return {
+			restrict: 'A',
+			link: function ( scope, element, attrs ) {
+				//tree id
+				var treeId = attrs.treeId;
+
+				//tree model
+				var treeModel = attrs.treeModel;
+
+				//node id
+				var nodeId = attrs.nodeId || 'id';
+
+				//node label
+				var nodeLabel = attrs.nodeLabel || 'label';
+
+				//children
+				var nodeChildren = attrs.nodeChildren || 'children';
+
+				//tree template
+
+                var template =
+                    '<ul>' +
+                    '<li data-ng-repeat="node in ' + treeModel + '">' +
+                        '<i ng-class="getGroupClass(node)" data-ng-click="' + treeId + '.selectNodeHead(node)"></i>' +
+                    '<span data-ng-class="getSelectedClass(node)" ng-dblclick="edit(node)" data-ng-click="' + treeId + '.selectNodeLabel(node)">{{node.' + nodeLabel + '}}</span>' +
+                    '<div data-ng-hide="node.collapsed" data-tree-id="' + treeId + '" data-tree-model="node.' + nodeChildren + '" data-node-id=' + nodeId + ' data-node-label=' + nodeLabel + ' data-node-children=' + nodeChildren + '></div>' +
+                    '</li>' +
+                    '</ul>';
+
+
+                //check tree id, tree model
+				if( treeId && treeModel ) {
+					//root node
+					if( attrs.angularTreeview ) {
+					
+						//create tree object if not exists
+						scope[treeId] = scope[treeId] || {};
+
+						//if node head clicks,
+						scope[treeId].selectNodeHead = scope[treeId].selectNodeHead || function( selectedNode ){
+
+							//Collapse or Expand
+							selectedNode.collapsed = !selectedNode.collapsed;
+                            scope[treeId].selectNodeLabel(selectedNode);
+						};
+
+						//if node label clicks,
+						scope[treeId].selectNodeLabel = scope[treeId].selectNodeLabel || function( selectedNode ){
+
+							//remove highlight from previous node
+							if( scope[treeId].currentNode && scope[treeId].currentNode.selected ) {
+								scope[treeId].currentNode.selected = undefined;
+							}
+
+							//set highlight to selected node
+							selectedNode.selected = 'selected';
+
+							//set currentNode
+							scope[treeId].currentNode = selectedNode;
+						};
+					}
+
+					//Rendering template.
+					element.html('').append( $compile( template )( scope ) );
+				}
+			}
+		};
+	}]);
+})( angular );
diff --git a/forms/common-themes/src/main/resources/theme/keycloak/common/resources/lib/angular/treeview/angular.treeview.min.js b/forms/common-themes/src/main/resources/theme/keycloak/common/resources/lib/angular/treeview/angular.treeview.min.js
new file mode 100644
index 0000000..e02a85b
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/keycloak/common/resources/lib/angular/treeview/angular.treeview.min.js
@@ -0,0 +1,9 @@
+/*
+	@license Angular Treeview version 0.1.6
+	ⓒ 2013 AHN JAE-HA http://github.com/eu81273/angular.treeview
+	License: MIT
+*/
+
+(function(f){f.module("angularTreeview",[]).directive("treeModel",function($compile){return{restrict:"A",link:function(b,h,c){var a=c.treeId,g=c.treeModel,e=c.nodeLabel||"label",d=c.nodeChildren||"children",e='<ul><li data-ng-repeat="node in '+g+'"><i class="collapsed" data-ng-show="node.'+d+'.length && node.collapsed" data-ng-click="'+a+'.selectNodeHead(node)"></i><i class="expanded" data-ng-show="node.'+d+'.length && !node.collapsed" data-ng-click="'+a+'.selectNodeHead(node)"></i><i class="normal" data-ng-hide="node.'+
+d+'.length"></i> <span data-ng-class="node.selected" data-ng-click="'+a+'.selectNodeLabel(node)">{{node.'+e+'}}</span><div data-ng-hide="node.collapsed" data-tree-id="'+a+'" data-tree-model="node.'+d+'" data-node-id='+(c.nodeId||"id")+" data-node-label="+e+" data-node-children="+d+"></div></li></ul>";a&&g&&(c.angularTreeview&&(b[a]=b[a]||{},b[a].selectNodeHead=b[a].selectNodeHead||function(a){a.collapsed=!a.collapsed},b[a].selectNodeLabel=b[a].selectNodeLabel||function(c){b[a].currentNode&&b[a].currentNode.selected&&
+(b[a].currentNode.selected=void 0);c.selected="selected";b[a].currentNode=c}),h.html('').append($compile(e)(b)))}}})})(angular);
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/keycloak/common/resources/lib/angular/treeview/css/angular.treeview.css b/forms/common-themes/src/main/resources/theme/keycloak/common/resources/lib/angular/treeview/css/angular.treeview.css
new file mode 100755
index 0000000..d0986e7
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/keycloak/common/resources/lib/angular/treeview/css/angular.treeview.css
@@ -0,0 +1,99 @@
+div[angular-treeview] {
+  /* prevent user selection */
+  -moz-user-select: -moz-none;
+  -khtml-user-select: none;
+  -webkit-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+
+  /* default */
+  font-family: Tahoma;
+  font-size:13px;
+  color: #555;
+  text-decoration: none;
+}
+
+div[tree-model] ul {
+  margin: 0;
+  padding: 0;
+  list-style: none; 
+  border: none;
+  overflow: hidden;
+}
+
+div[tree-model] li {
+  position: relative;
+  padding: 0 0 0 20px;
+  line-height: 20px;
+}
+
+div[tree-model] li .expanded {
+  padding: 1px 10px;
+  background-image: url("../img/folder.png");
+  background-repeat: no-repeat;
+}
+
+div[tree-model] li .collapsed {
+  padding: 1px 10px;
+  background-image: url("../img/folder-closed.png");
+  background-repeat: no-repeat;
+}
+
+div[tree-model] li .normal {
+  padding: 1px 10px;
+  background-image: url("../img/file.png");
+  background-repeat: no-repeat;
+}
+
+div[tree-model] li i, div[tree-model] li span {
+  cursor: pointer;
+}
+
+div[tree-model] li .selected {
+    background-color: #aaddff;
+    font-weight: bold;
+    padding: 1px 5px;
+}
+
+div[tree-model] li .cut {
+    font-weight: bold;
+    color: gray
+}
+
+/*
+.angular-ui-tree-handle {
+    cursor: grab;
+    text-decoration: none;
+    font-weight: bold;
+    -webkit-box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    box-sizing: border-box;
+    min-height: 20px;
+    line-height: 20px;
+}
+*/
+
+.angular-ui-tree-handle {
+    /* background: #f8faff; */
+    /*
+    color: #7c9eb2;    */
+    border: 1px solid #dae2ea;
+    padding: 10px 10px;
+    cursor: pointer;
+}
+
+.expanded-folder {
+    padding: 1px 10px;
+    background-image: url("../img/folder.png");
+    background-repeat: no-repeat;
+    cursor: pointer;
+}
+
+.collapsed-folder {
+    padding: 1px 10px;
+    background-image: url("../img/folder-closed.png");
+    background-repeat: no-repeat;
+    cursor: pointer;
+}
+
+
diff --git a/forms/common-themes/src/main/resources/theme/keycloak/common/resources/lib/angular/treeview/img/file.png b/forms/common-themes/src/main/resources/theme/keycloak/common/resources/lib/angular/treeview/img/file.png
new file mode 100644
index 0000000..ffd22db
Binary files /dev/null and b/forms/common-themes/src/main/resources/theme/keycloak/common/resources/lib/angular/treeview/img/file.png differ
diff --git a/forms/common-themes/src/main/resources/theme/keycloak/common/resources/lib/angular/treeview/img/folder.png b/forms/common-themes/src/main/resources/theme/keycloak/common/resources/lib/angular/treeview/img/folder.png
new file mode 100644
index 0000000..fdad546
Binary files /dev/null and b/forms/common-themes/src/main/resources/theme/keycloak/common/resources/lib/angular/treeview/img/folder.png differ
diff --git a/forms/common-themes/src/main/resources/theme/keycloak/common/resources/lib/angular/treeview/img/folder-closed.png b/forms/common-themes/src/main/resources/theme/keycloak/common/resources/lib/angular/treeview/img/folder-closed.png
new file mode 100644
index 0000000..9c8489c
Binary files /dev/null and b/forms/common-themes/src/main/resources/theme/keycloak/common/resources/lib/angular/treeview/img/folder-closed.png differ
diff --git a/forms/common-themes/src/main/resources/theme/keycloak/common/resources/lib/angular/treeview/LICENSE b/forms/common-themes/src/main/resources/theme/keycloak/common/resources/lib/angular/treeview/LICENSE
new file mode 100644
index 0000000..514acd3
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/keycloak/common/resources/lib/angular/treeview/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2013 Steve
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/forms/common-themes/src/main/resources/theme/keycloak/common/resources/lib/angular/treeview/README.md b/forms/common-themes/src/main/resources/theme/keycloak/common/resources/lib/angular/treeview/README.md
new file mode 100644
index 0000000..3c2a7b5
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/keycloak/common/resources/lib/angular/treeview/README.md
@@ -0,0 +1,122 @@
+Angular Treeview
+================
+
+Pure [AngularJS](http://www.angularjs.org) based tree menu directive.
+
+[![ScreenShot](https://github.com/eu81273/angular.treeview/raw/master/img/preview.png)](http://jsfiddle.net/eu81273/8LWUc/)
+
+## Installation
+
+Copy the script and css into your project and add a script and link tag to your page.
+
+```html
+<script type="text/javascript" src="/angular.treeview.js"></script>
+<link rel="stylesheet" type="text/css" href="css/angular.treeview.css">
+```
+
+Add a dependency to your application module.
+
+```javascript
+angular.module('myApp', ['angularTreeview']);
+```
+
+Add a tree to your application. See [Usage](#usage).
+
+## Usage
+
+Attributes of angular treeview are below.
+
+- angular-treeview: the treeview directive.
+- tree-id : each tree's unique id.
+- tree-model : the tree model on $scope.
+- node-id : each node's id.
+- node-label : each node's label.
+- node-children: each node's children.
+
+Here is a simple example.
+
+
+```html
+<div
+    data-angular-treeview="true"
+	data-tree-id="abc"
+	data-tree-model="treedata"
+	data-node-id="id"
+	data-node-label="label"
+	data-node-children="children" >
+</div>
+```
+
+Example model:
+
+```javascript
+$scope.treedata = 
+[
+	{ "label" : "User", "id" : "role1", "children" : [
+		{ "label" : "subUser1", "id" : "role11", "children" : [] },
+		{ "label" : "subUser2", "id" : "role12", "children" : [
+			{ "label" : "subUser2-1", "id" : "role121", "children" : [
+				{ "label" : "subUser2-1-1", "id" : "role1211", "children" : [] },
+				{ "label" : "subUser2-1-2", "id" : "role1212", "children" : [] }
+			]}
+		]}
+	]},
+	{ "label" : "Admin", "id" : "role2", "children" : [] },
+	{ "label" : "Guest", "id" : "role3", "children" : [] }
+];	 
+```
+
+## Selection
+
+If tree node is selected, then that selected tree node is saved to $scope."TREE ID".currentNode. By using $watch, the controller can recognize the tree selection.
+
+
+```javascript
+$scope.$watch( 'abc.currentNode', function( newObj, oldObj ) {
+    if( $scope.abc && angular.isObject($scope.abc.currentNode) ) {
+        console.log( 'Node Selected!!' );
+        console.log( $scope.abc.currentNode );
+    }
+}, false);
+```
+
+## Examples
+
+#### Basic example
+[![ScreenShot](https://github.com/eu81273/angular.treeview/raw/master/img/jsfiddle01.png)](http://jsfiddle.net/eu81273/8LWUc/)
+
+[jsFiddle - http://jsfiddle.net/eu81273/8LWUc/](http://jsfiddle.net/eu81273/8LWUc/)
+
+#### Multiple treeview example
+[![ScreenShot](https://github.com/eu81273/angular.treeview/raw/master/img/jsfiddle02.png)](http://jsfiddle.net/eu81273/b9Pnw/)
+
+[jsFiddle - http://jsfiddle.net/eu81273/b9Pnw/](http://jsfiddle.net/eu81273/b9Pnw/)
+
+## Browser Compatibility
+
+Same with AngularJS. Safari, Chrome, Firefox, Opera, IE8, IE9 and mobile browsers (Android, Chrome Mobile, iOS Safari).
+
+## Changelogs
+
+#### version 0.1.6
+- Fixed the bug that 'null' string appears before each 'div' generated. (Thanks to Iaac)
+
+#### version 0.1.5
+- support multiple treeview. (Thanks to Axel Pesme)
+
+#### version 0.1.4
+- prevented memory leaks.
+
+#### version 0.1.3
+- removed unnecessary codes.
+
+#### version 0.1.2
+- removed some jQuery dependency. (Issue #2)
+
+## License
+
+The MIT License.
+
+Copyright ⓒ 2013 AHN JAE-HA.
+
+See [LICENSE](https://github.com/eu81273/angular.treeview/blob/master/LICENSE)
diff --git a/forms/common-themes/src/main/resources/theme/keycloak/email/html/identity-provider-link.ftl b/forms/common-themes/src/main/resources/theme/keycloak/email/html/identity-provider-link.ftl
new file mode 100644
index 0000000..9c2db80
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/keycloak/email/html/identity-provider-link.ftl
@@ -0,0 +1,5 @@
+<html>
+<body>
+${msg("identityProviderLinkBodyHtml", identityProviderAlias, realmName, identityProviderContext.username, link, linkExpiration)}
+</body>
+</html>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/keycloak/email/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/keycloak/email/messages/messages_en.properties
index fd9d909..f4a945c 100755
--- a/forms/common-themes/src/main/resources/theme/keycloak/email/messages/messages_en.properties
+++ b/forms/common-themes/src/main/resources/theme/keycloak/email/messages/messages_en.properties
@@ -1,6 +1,9 @@
 emailVerificationSubject=Verify email
 emailVerificationBody=Someone has created a {2} account with this email address. If this was you, click the link below to verify your email address\n\n{0}\n\nThis link will expire within {1} minutes.\n\nIf you didn''t create this account, just ignore this message.
 emailVerificationBodyHtml=<p>Someone has created a {2} account with this email address. If this was you, click the link below to verify your email address</p><p><a href="{0}">{0}</a></p><p>This link will expire within {1} minutes.</p><p>If you didn''t create this account, just ignore this message.</p>
+identityProviderLinkSubject=Link {0}
+identityProviderLinkBody=Someone wants to link your "{1}" account with "{0}" account of user {2} . If this was you, click the link below to link accounts\n\n{3}\n\nThis link will expire within {4} minutes.\n\nIf you don''t want to link account, just ignore this message. If you link accounts, you will be able to login to {1} through {0}.
+identityProviderLinkBodyHtml=<p>Someone wants to link your <b>{1}</b> account with <b>{0}</b> account of user {2} . If this was you, click the link below to link accounts</p><p><a href="{3}">{3}</a></p><p>This link will expire within {4} minutes.</p><p>If you don''t want to link account, just ignore this message. If you link accounts, you will be able to login to {1} through {0}.</p>
 passwordResetSubject=Reset password
 passwordResetBody=Someone just requested to change your {2} account''s credentials. If this was you, click on the link below to reset them.\n\n{0}\n\nThis link and code will expire within {1} minutes.\n\nIf you don''t want to reset your credentials, just ignore this message and nothing will be changed.
 passwordResetBodyHtml=<p>Someone just requested to change your {2} account''s credentials. If this was you, click on the link below to reset them.</p><p><a href="{0}">{0}</a></p><p>This link will expire within {1} minutes.</p><p>If you don''t want to reset your credentials, just ignore this message and nothing will be changed.</p>
diff --git a/forms/common-themes/src/main/resources/theme/keycloak/email/messages/messages_es.properties b/forms/common-themes/src/main/resources/theme/keycloak/email/messages/messages_es.properties
new file mode 100644
index 0000000..1616014
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/keycloak/email/messages/messages_es.properties
@@ -0,0 +1,21 @@
+emailVerificationSubject=Verificaci\u00F3n de email
+emailVerificationBody=Alguien ha creado una cuenta de {2} con esta direcci\u00F3n de email. Si has sido t\u00FA, haz click en el enlace siguiente para verificar tu direcci\u00F3n de email.\n\n{0}\n\nEste enlace expirar\u00E1 en {1} minutos.\n\nSi t\u00FA no has creado esta cuenta, simplemente ignora este mensaje.
+emailVerificationBodyHtml=<p>Alguien ha creado una cuenta de {2} con esta direcci\u00F3n de email. Si has sido t\u00FA, haz click en el enlace siguiente para verificar tu direcci\u00F3n de email.</p><p><a href=\"{0}\">{0}</a></p><p>Este enlace expirar\u00E1 en {1} minutos.</p><p>Si t\u00FA no has creado esta cuenta, simplemente ignora este mensaje.</p>
+passwordResetSubject=Reiniciar contrase\u00F1a
+passwordResetBody=Alguien ha solicitado cambiar las credenciales de tu cuenta de {2}. Si has sido t\u00FA, haz clic en el enlace siguiente para reiniciarlas.\n\n{0}\n\nEste enlace expirar\u00E1 en {1} minutos.\n\nSi no quieres reiniciar tus credenciales, simplemente ignora este mensaje y no se realizar\u00E1 ning\u00FAn cambio.
+passwordResetBodyHtml=<p>Alguien ha solicitado cambiar las credenciales de tu cuenta de {2}. Si has sido t\u00FA, haz clic en el enlace siguiente para reiniciarlas.</p><p><a href=\"{0}\">{0}</a></p><p>Este enlace expirar\u00E1 en {1} minutos.</p><p>Si no quieres reiniciar tus credenciales, simplemente ignora este mensaje y no se realizar\u00E1 ning\u00FAn cambio.</p>
+executeActionsSubject=Actualiza tu cuenta
+executeActionsBody=El administrador ha solicitado que actualices tu cuenta de {2}. Haz clic en el enlace inferior para iniciar este proceso.\n\n{0}\n\nEste enlace expirar\u00E1 en {1} minutes.\n\nSi no est\u00E1s al tanto de que el administrador haya solicitado esto, simplemente ignora este mensaje y no se realizar\u00E1 ning\u00FAn cambio.
+executeActionsBodyHtml=<p>El administrador ha solicitado que actualices tu cuenta de {2}. Haz clic en el enlace inferior para iniciar este proceso.</p><p><a href=\"{0}\">{0}</a></p><p>Este enlace expirar\u00E1 en {1} minutes.</p><p>Si no est\u00E1s al tanto de que el administrador haya solicitado esto, simplemente ignora este mensaje y no se realizar\u00E1 ning\u00FAn cambio.</p>
+eventLoginErrorSubject=Fallo en el inicio de sesi\u00F3n
+eventLoginErrorBody=Se ha detectado un intento de acceso fallido a tu cuenta el {0} desde {1}. Si no has sido t\u00FA, por favor contacta con el administrador.
+eventLoginErrorBodyHtml=<p>Se ha detectado un intento de acceso fallido a tu cuenta el {0} desde {1}. Si no has sido t\u00FA, por favor contacta con el administrador.</p>
+eventRemoveTotpSubject=Borrado TOTP
+eventRemoveTotpBody=TOTP fue eliminado de tu cuenta el {0} desde {1}. Si no has sido t\u00FA, por favor contacta con el administrador.
+eventRemoveTotpBodyHtml=<p>TOTP fue eliminado de tu cuenta el {0} desde {1}. Si no has sido t\u00FA, por favor contacta con el administrador.</p>
+eventUpdatePasswordSubject=Actualizaci\u00F3n de contrase\u00F1a
+eventUpdatePasswordBody=Tu contrase\u00F1a se ha actualizado el {0} desde {1}. Si no has sido t\u00FA, por favor contacta con el administrador.
+eventUpdatePasswordBodyHtml=<p>Tu contrase\u00F1a se ha actualizado el {0} desde {1}. Si no has sido t\u00FA, por favor contacta con el administrador.</p>
+eventUpdateTotpSubject=Actualizaci\u00F3n de TOTP
+eventUpdateTotpBody=TOTP se ha actualizado en tu cuenta el {0} desde {1}. Si no has sido t\u00FA, por favor contacta con el administrador.
+eventUpdateTotpBodyHtml=<p>TOTP se ha actualizado en tu cuenta el {0} desde {1}. Si no has sido t\u00FA, por favor contacta con el administrador.</p>
diff --git a/forms/common-themes/src/main/resources/theme/keycloak/email/text/identity-provider-link.ftl b/forms/common-themes/src/main/resources/theme/keycloak/email/text/identity-provider-link.ftl
new file mode 100644
index 0000000..a8c0d54
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/keycloak/email/text/identity-provider-link.ftl
@@ -0,0 +1 @@
+${msg("identityProviderLinkBody", identityProviderAlias, realmName, identityProviderContext.username, link, linkExpiration)}
\ No newline at end of file
diff --git a/forms/email-api/src/main/java/org/keycloak/email/EmailProvider.java b/forms/email-api/src/main/java/org/keycloak/email/EmailProvider.java
index 2304ce5..151eec7 100755
--- a/forms/email-api/src/main/java/org/keycloak/email/EmailProvider.java
+++ b/forms/email-api/src/main/java/org/keycloak/email/EmailProvider.java
@@ -10,10 +10,14 @@ import org.keycloak.provider.Provider;
  */
 public interface EmailProvider extends Provider {
 
+    String IDENTITY_PROVIDER_BROKER_CONTEXT = "identityProviderBrokerCtx";
+
     public EmailProvider setRealm(RealmModel realm);
 
     public EmailProvider setUser(UserModel user);
 
+    public EmailProvider setAttribute(String name, Object value);
+
     public void sendEvent(Event event) throws EmailException;
 
     /**
@@ -26,6 +30,11 @@ public interface EmailProvider extends Provider {
     public void sendPasswordReset(String link, long expirationInMinutes) throws EmailException;
 
     /**
+     * Send to confirm that user wants to link his account with identity broker link
+     */
+    void sendConfirmIdentityBrokerLink(String link, long expirationInMinutes) throws EmailException;
+
+    /**
      * Change password email requested by admin
      *
      * @param link
diff --git a/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java b/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java
index 9f55e37..25b0166 100755
--- a/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java
+++ b/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java
@@ -1,8 +1,11 @@
 package org.keycloak.email.freemarker;
 
 import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Properties;
@@ -17,6 +20,8 @@ import javax.mail.internet.MimeMessage;
 import javax.mail.internet.MimeMultipart;
 
 import org.jboss.logging.Logger;
+import org.keycloak.broker.provider.BrokeredIdentityContext;
+import org.keycloak.common.util.ObjectUtil;
 import org.keycloak.email.EmailException;
 import org.keycloak.email.EmailProvider;
 import org.keycloak.email.freemarker.beans.EventBean;
@@ -28,6 +33,7 @@ import org.keycloak.freemarker.FreeMarkerUtil;
 import org.keycloak.freemarker.Theme;
 import org.keycloak.freemarker.ThemeProvider;
 import org.keycloak.freemarker.beans.MessageFormatterMethod;
+import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
@@ -43,6 +49,7 @@ public class FreeMarkerEmailProvider implements EmailProvider {
     private FreeMarkerUtil freeMarker;
     private RealmModel realm;
     private UserModel user;
+    private final Map<String, Object> attributes = new HashMap<String, Object>();
 
     public FreeMarkerEmailProvider(KeycloakSession session, FreeMarkerUtil freeMarker) {
         this.session = session;
@@ -62,6 +69,12 @@ public class FreeMarkerEmailProvider implements EmailProvider {
     }
 
     @Override
+    public EmailProvider setAttribute(String name, Object value) {
+        attributes.put(name, value);
+        return this;
+    }
+
+    @Override
     public void sendEvent(Event event) throws EmailException {
         Map<String, Object> attributes = new HashMap<String, Object>();
         attributes.put("user", new ProfileBean(user));
@@ -77,20 +90,41 @@ public class FreeMarkerEmailProvider implements EmailProvider {
         attributes.put("link", link);
         attributes.put("linkExpiration", expirationInMinutes);
 
-        String realmName = realm.getName().substring(0, 1).toUpperCase() + realm.getName().substring(1);
+        String realmName = ObjectUtil.capitalize(realm.getName());
         attributes.put("realmName", realmName);
 
         send("passwordResetSubject", "password-reset.ftl", attributes);
     }
 
     @Override
+    public void sendConfirmIdentityBrokerLink(String link, long expirationInMinutes) throws EmailException {
+        Map<String, Object> attributes = new HashMap<String, Object>();
+        attributes.put("user", new ProfileBean(user));
+        attributes.put("link", link);
+        attributes.put("linkExpiration", expirationInMinutes);
+
+        String realmName = ObjectUtil.capitalize(realm.getName());
+        attributes.put("realmName", realmName);
+
+        BrokeredIdentityContext brokerContext = (BrokeredIdentityContext) this.attributes.get(IDENTITY_PROVIDER_BROKER_CONTEXT);
+        String idpAlias = brokerContext.getIdpConfig().getAlias();
+        idpAlias = ObjectUtil.capitalize(idpAlias);
+
+        attributes.put("identityProviderContext", brokerContext);
+        attributes.put("identityProviderAlias", idpAlias);
+
+        List<Object> subjectAttrs = Arrays.<Object>asList(idpAlias);
+        send("identityProviderLinkSubject", subjectAttrs, "identity-provider-link.ftl", attributes);
+    }
+
+    @Override
     public void sendExecuteActions(String link, long expirationInMinutes) throws EmailException {
         Map<String, Object> attributes = new HashMap<String, Object>();
         attributes.put("user", new ProfileBean(user));
         attributes.put("link", link);
         attributes.put("linkExpiration", expirationInMinutes);
 
-        String realmName = realm.getName().substring(0, 1).toUpperCase() + realm.getName().substring(1);
+        String realmName = ObjectUtil.capitalize(realm.getName());
         attributes.put("realmName", realmName);
 
         send("executeActionsSubject", "executeActions.ftl", attributes);
@@ -104,13 +138,17 @@ public class FreeMarkerEmailProvider implements EmailProvider {
         attributes.put("link", link);
         attributes.put("linkExpiration", expirationInMinutes);
 
-        String realmName = realm.getName().substring(0, 1).toUpperCase() + realm.getName().substring(1);
+        String realmName = ObjectUtil.capitalize(realm.getName());
         attributes.put("realmName", realmName);
 
         send("emailVerificationSubject", "email-verification.ftl", attributes);
     }
 
     private void send(String subjectKey, String template, Map<String, Object> attributes) throws EmailException {
+        send(subjectKey, Collections.emptyList(), template, attributes);
+    }
+
+    private void send(String subjectKey, List<Object> subjectAttributes, String template, Map<String, Object> attributes) throws EmailException {
         try {
             ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
             Theme theme = themeProvider.getTheme(realm.getEmailTheme(), Theme.Type.EMAIL);
@@ -118,7 +156,7 @@ public class FreeMarkerEmailProvider implements EmailProvider {
             attributes.put("locale", locale);
             Properties rb = theme.getMessages(locale);
             attributes.put("msg", new MessageFormatterMethod(locale, rb));
-            String subject = new MessageFormat(rb.getProperty(subjectKey,subjectKey),locale).format(new Object[0]);
+            String subject = new MessageFormat(rb.getProperty(subjectKey,subjectKey),locale).format(subjectAttributes.toArray());
             String textTemplate = String.format("text/%s", template);
             String textBody;
             try {
@@ -216,7 +254,7 @@ public class FreeMarkerEmailProvider implements EmailProvider {
     private String toCamelCase(EventType event){
         StringBuilder sb = new StringBuilder("event");
         for(String s : event.name().toString().toLowerCase().split("_")){
-            sb.append(s.substring(0,1).toUpperCase()).append(s.substring(1));
+            sb.append(ObjectUtil.capitalize(s));
         }
         return sb.toString();
     }
diff --git a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsPages.java b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsPages.java
index e34b0ef..9a44db4 100644
--- a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsPages.java
+++ b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsPages.java
@@ -5,6 +5,8 @@ package org.keycloak.login;
  */
 public enum LoginFormsPages {
 
-    LOGIN, LOGIN_TOTP, LOGIN_CONFIG_TOTP, LOGIN_VERIFY_EMAIL, OAUTH_GRANT, LOGIN_RESET_PASSWORD, LOGIN_UPDATE_PASSWORD, REGISTER, INFO, ERROR, LOGIN_UPDATE_PROFILE, CODE;
+    LOGIN, LOGIN_TOTP, LOGIN_CONFIG_TOTP, LOGIN_VERIFY_EMAIL,
+    LOGIN_IDP_LINK_CONFIRM, LOGIN_IDP_LINK_EMAIL,
+    OAUTH_GRANT, LOGIN_RESET_PASSWORD, LOGIN_UPDATE_PASSWORD, REGISTER, INFO, ERROR, LOGIN_UPDATE_PROFILE, CODE;
 
 }
diff --git a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
index fccfce1..eaeb4b4 100755
--- a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
+++ b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
@@ -23,6 +23,13 @@ import org.keycloak.provider.Provider;
  */
 public interface LoginFormsProvider extends Provider {
 
+    String UPDATE_PROFILE_CONTEXT_ATTR = "updateProfileCtx";
+
+    String IDENTITY_PROVIDER_BROKER_CONTEXT = "identityProviderBrokerCtx";
+
+    String USERNAME_EDIT_DISABLED = "usernameEditDisabled";
+
+
     /**
      * Adds a script to the html header
      *
@@ -44,6 +51,12 @@ public interface LoginFormsProvider extends Provider {
 
     public Response createInfoPage();
 
+    public Response createUpdateProfilePage();
+
+    public Response createIdpLinkConfirmLinkPage();
+
+    public Response createIdpLinkEmailPage();
+
     public Response createErrorPage();
 
     public Response createOAuthGrant(ClientSessionModel clientSessionModel);
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
index 6125d0b..d1b4df9 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
@@ -19,6 +19,10 @@ package org.keycloak.login.freemarker;
 import org.jboss.logging.Logger;
 import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
 import org.keycloak.OAuth2Constants;
+import org.keycloak.authentication.requiredactions.util.UpdateProfileContext;
+import org.keycloak.authentication.requiredactions.util.UserUpdateProfileContext;
+import org.keycloak.broker.provider.BrokeredIdentityContext;
+import org.keycloak.common.util.ObjectUtil;
 import org.keycloak.email.EmailException;
 import org.keycloak.email.EmailProvider;
 import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
@@ -48,6 +52,7 @@ import org.keycloak.login.freemarker.model.UrlBean;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.Constants;
+import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RealmModel;
@@ -129,6 +134,9 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
                 page = LoginFormsPages.LOGIN_CONFIG_TOTP;
                 break;
             case UPDATE_PROFILE:
+                UpdateProfileContext userBasedContext = new UserUpdateProfileContext(realm, user);
+                this.attributes.put(UPDATE_PROFILE_CONTEXT_ATTR, userBasedContext);
+
                 actionMessage = Messages.UPDATE_PROFILE;
                 page = LoginFormsPages.LOGIN_UPDATE_PROFILE;
                 break;
@@ -140,7 +148,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
                 try {
                     UriBuilder builder = Urls.loginActionEmailVerificationBuilder(uriInfo.getBaseUri());
                     builder.queryParam(OAuth2Constants.CODE, accessCode);
-                    builder.queryParam("key", clientSession.getNote(Constants.VERIFY_EMAIL_KEY));
+                    builder.queryParam(Constants.KEY, clientSession.getNote(Constants.VERIFY_EMAIL_KEY));
 
                     String link = builder.build(realm.getName()).toString();
                     long expiration = TimeUnit.SECONDS.toMinutes(realm.getAccessCodeLifespanUserAction());
@@ -222,6 +230,8 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
                 }
             }
             attributes.put("message", wholeMessage);
+        } else {
+            attributes.put("message", null);
         }
         attributes.put("messagesPerField", messagesPerField);
 
@@ -237,7 +247,11 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
 
         if (realm != null) {
             attributes.put("realm", new RealmBean(realm));
-            attributes.put("social", new IdentityProviderBean(realm, baseUri, uriInfo));
+
+            List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
+            identityProviders = LoginFormsUtil.filterIdentityProviders(identityProviders, session, realm, attributes, formData);
+            attributes.put("social", new IdentityProviderBean(realm, identityProviders, baseUri, uriInfo));
+
             attributes.put("url", new UrlBean(realm, theme, baseUri, this.actionUri));
 
             if (realm.isInternationalizationEnabled()) {
@@ -268,7 +282,17 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
                 attributes.put("totp", new TotpBean(realm, user, baseUri));
                 break;
             case LOGIN_UPDATE_PROFILE:
-                attributes.put("user", new ProfileBean(user, formData));
+                UpdateProfileContext userCtx = (UpdateProfileContext) attributes.get(LoginFormsProvider.UPDATE_PROFILE_CONTEXT_ATTR);
+                attributes.put("user", new ProfileBean(userCtx, formData));
+                break;
+            case LOGIN_IDP_LINK_CONFIRM:
+            case LOGIN_IDP_LINK_EMAIL:
+                BrokeredIdentityContext brokerContext = (BrokeredIdentityContext) this.attributes.get(IDENTITY_PROVIDER_BROKER_CONTEXT);
+                String idpAlias = brokerContext.getIdpConfig().getAlias();
+                idpAlias = ObjectUtil.capitalize(idpAlias);
+
+                attributes.put("brokerContext", brokerContext);
+                attributes.put("idpAlias", idpAlias);
                 break;
             case REGISTER:
                 attributes.put("register", new RegisterBean(formData));
@@ -371,7 +395,11 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
 
         if (realm != null) {
             attributes.put("realm", new RealmBean(realm));
-            attributes.put("social", new IdentityProviderBean(realm, baseUri, uriInfo));
+
+            List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
+            identityProviders = LoginFormsUtil.filterIdentityProviders(identityProviders, session, realm, attributes, formData);
+            attributes.put("social", new IdentityProviderBean(realm, identityProviders, baseUri, uriInfo));
+
             attributes.put("url", new UrlBean(realm, theme, baseUri, this.actionUri));
             attributes.put("requiredActionUrl", new RequiredActionUrlFormatterMethod(realm, baseUri));
 
@@ -424,6 +452,32 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
     }
 
     @Override
+    public Response createUpdateProfilePage() {
+        // Don't display initial message if we already have some errors
+        if (messageType != MessageType.ERROR) {
+            setMessage(MessageType.WARNING, Messages.UPDATE_PROFILE);
+        }
+
+        return createResponse(LoginFormsPages.LOGIN_UPDATE_PROFILE);
+    }
+
+
+    @Override
+    public Response createIdpLinkConfirmLinkPage() {
+        return createResponse(LoginFormsPages.LOGIN_IDP_LINK_CONFIRM);
+    }
+
+    @Override
+    public Response createIdpLinkEmailPage() {
+        BrokeredIdentityContext brokerContext = (BrokeredIdentityContext) this.attributes.get(IDENTITY_PROVIDER_BROKER_CONTEXT);
+        String idpAlias = brokerContext.getIdpConfig().getAlias();
+        idpAlias = ObjectUtil.capitalize(idpAlias);;
+        setMessage(MessageType.WARNING, Messages.LINK_IDP, idpAlias);
+
+        return createResponse(LoginFormsPages.LOGIN_IDP_LINK_EMAIL);
+    }
+
+    @Override
     public Response createErrorPage() {
         if (status == null) {
             status = Response.Status.INTERNAL_SERVER_ERROR;
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/LoginFormsUtil.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/LoginFormsUtil.java
new file mode 100644
index 0000000..bbcf1c0
--- /dev/null
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/LoginFormsUtil.java
@@ -0,0 +1,58 @@
+package org.keycloak.login.freemarker;
+
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.ws.rs.core.MultivaluedMap;
+
+import org.keycloak.login.LoginFormsProvider;
+import org.keycloak.models.FederatedIdentityModel;
+import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+
+/**
+ * Various util methods, so the logic is not hardcoded in freemarker beans
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class LoginFormsUtil {
+
+    // Display just those identityProviders on login screen, which are already linked to "known" established user
+    public static List<IdentityProviderModel> filterIdentityProviders(List<IdentityProviderModel> providers, KeycloakSession session, RealmModel realm,
+                                                                      Map<String, Object> attributes, MultivaluedMap<String, String> formData) {
+
+        Boolean usernameEditDisabled = (Boolean) attributes.get(LoginFormsProvider.USERNAME_EDIT_DISABLED);
+        if (usernameEditDisabled != null && usernameEditDisabled) {
+            String username = formData.getFirst(UserModel.USERNAME);
+            if (username == null) {
+                throw new IllegalStateException("USERNAME_EDIT_DISABLED but username not known");
+            }
+
+            UserModel user = session.users().getUserByUsername(username, realm);
+            if (user == null || !user.isEnabled()) {
+                throw new IllegalStateException("User " + username + " not found or disabled");
+            }
+
+            Set<FederatedIdentityModel> fedLinks = session.users().getFederatedIdentities(user, realm);
+            Set<String> federatedIdentities = new HashSet<>();
+            for (FederatedIdentityModel fedLink : fedLinks) {
+                federatedIdentities.add(fedLink.getIdentityProvider());
+            }
+
+            List<IdentityProviderModel> result = new LinkedList<>();
+            for (IdentityProviderModel idp : providers) {
+                if (federatedIdentities.contains(idp.getAlias())) {
+                    result.add(idp);
+                }
+            }
+            return result;
+        } else {
+            return providers;
+        }
+    }
+}
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/IdentityProviderBean.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/IdentityProviderBean.java
index 23c6e69..4d4f1e3 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/IdentityProviderBean.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/IdentityProviderBean.java
@@ -44,9 +44,8 @@ public class IdentityProviderBean {
     private List<IdentityProvider> providers;
     private RealmModel realm;
 
-    public IdentityProviderBean(RealmModel realm, URI baseURI, UriInfo uriInfo) {
+    public IdentityProviderBean(RealmModel realm, List<IdentityProviderModel> identityProviders, URI baseURI, UriInfo uriInfo) {
         this.realm = realm;
-        List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
 
         if (!identityProviders.isEmpty()) {
             Set<IdentityProvider> orderedSet = new TreeSet<>(IdentityProviderComparator.INSTANCE);
@@ -57,7 +56,7 @@ public class IdentityProviderBean {
             }
 
             if (!orderedSet.isEmpty()) {
-                providers = new LinkedList<IdentityProvider>(orderedSet);
+                providers = new LinkedList<>(orderedSet);
                 displaySocial = true;
             }
         }
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/ProfileBean.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/ProfileBean.java
index e730c14..b2d6b1d 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/ProfileBean.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/ProfileBean.java
@@ -28,7 +28,7 @@ import java.util.Map;
 import javax.ws.rs.core.MultivaluedMap;
 
 import org.jboss.logging.Logger;
-import org.keycloak.models.UserModel;
+import org.keycloak.authentication.requiredactions.util.UpdateProfileContext;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -38,12 +38,12 @@ public class ProfileBean {
 
     private static final Logger logger = Logger.getLogger(ProfileBean.class);
 
-    private UserModel user;
+    private UpdateProfileContext user;
     private MultivaluedMap<String, String> formData;
 
     private final Map<String, String> attributes = new HashMap<>();
 
-    public ProfileBean(UserModel user, MultivaluedMap<String, String> formData) {
+    public ProfileBean(UpdateProfileContext user, MultivaluedMap<String, String> formData) {
         this.user = user;
         this.formData = formData;
 
@@ -70,6 +70,10 @@ public class ProfileBean {
 
     }
 
+    public boolean isEditUsernameAllowed() {
+        return user.isEditUsernameAllowed();
+    }
+
     public String getUsername() { return formData != null ? formData.getFirst("username") : user.getUsername(); }
 
     public String getFirstName() {
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java
index 25ebc83..ff83c43 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java
@@ -90,6 +90,10 @@ public class UrlBean {
         return Urls.loginActionEmailVerification(baseURI, realm).toString();
     }
 
+    public String getFirstBrokerLoginUrl() {
+        return Urls.firstBrokerLoginProcessor(baseURI, realm).toString();
+    }
+
     public String getOauthAction() {
         if (this.actionuri != null) {
             return this.actionuri.getPath();
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/Templates.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/Templates.java
index 785b186..e63f49f 100644
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/Templates.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/Templates.java
@@ -17,6 +17,10 @@ public class Templates {
                 return "login-config-totp.ftl";
             case LOGIN_VERIFY_EMAIL:
                 return "login-verify-email.ftl";
+            case LOGIN_IDP_LINK_CONFIRM:
+                return "login-idp-link-confirm.ftl";
+            case LOGIN_IDP_LINK_EMAIL:
+                return "login-idp-link-email.ftl";
             case OAUTH_GRANT:
                 return "login-oauth-grant.ftl";
             case LOGIN_RESET_PASSWORD:
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/AuthenticatedActionsHandler.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/AuthenticatedActionsHandler.java
index 5e243de..8083654 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/AuthenticatedActionsHandler.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/AuthenticatedActionsHandler.java
@@ -57,7 +57,7 @@ public class AuthenticatedActionsHandler {
     protected boolean abortTokenResponse() {
         if (facade.getSecurityContext() == null) {
             log.debugv("Not logged in, sending back 401: {0}",facade.getRequest().getURI());
-            facade.getResponse().setStatus(401);
+            facade.getResponse().sendError(401);
             facade.getResponse().end();
             return true;
         }
@@ -94,7 +94,7 @@ public class AuthenticatedActionsHandler {
                     log.debugv("allowedOrigins did not contain origin");
 
                 }
-                facade.getResponse().setStatus(403);
+                facade.getResponse().sendError(403);
                 facade.getResponse().end();
                 return true;
             }
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
index 908d239..d341003 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
@@ -174,23 +174,7 @@ public class OAuthRequestAuthenticator {
         final String state = getStateCode();
         final String redirect = getRedirectUri(state);
         if (redirect == null) {
-            return new AuthChallenge() {
-                @Override
-                public boolean challenge(HttpFacade exchange) {
-                    exchange.getResponse().setStatus(403);
-                    return true;
-                }
-
-                @Override
-                public boolean errorPage() {
-                    return true;
-                }
-
-                @Override
-                public int getResponseCode() {
-                    return 403;
-                }
-            };
+            return challenge(403);
         }
         return new AuthChallenge() {
 
@@ -283,7 +267,7 @@ public class OAuthRequestAuthenticator {
 
             @Override
             public boolean challenge(HttpFacade exchange) {
-                exchange.getResponse().setStatus(code);
+                exchange.getResponse().sendError(code);
                 return true;
             }
         };
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java
index b9bdb0d..6e954e7 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java
@@ -57,7 +57,7 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext 
     }
 
     public boolean isActive() {
-        return this.token.isActive() && this.token.getIssuedAt() > deployment.getNotBefore();
+        return token != null && this.token.isActive() && this.token.getIssuedAt() > deployment.getNotBefore();
     }
 
     public KeycloakDeployment getDeployment() {
@@ -111,6 +111,7 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext 
             log.debug("Token Verification succeeded!");
         } catch (VerificationException e) {
             log.error("failed verification of token");
+            return false;
         }
         if (response.getNotBeforePolicy() > deployment.getNotBefore()) {
             deployment.setNotBefore(response.getNotBeforePolicy());
diff --git a/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/HttpFacade.java b/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/HttpFacade.java
index fb3804e..cf6e0d5 100755
--- a/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/HttpFacade.java
+++ b/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/HttpFacade.java
@@ -56,6 +56,7 @@ public interface HttpFacade {
         void resetCookie(String name, String path);
         void setCookie(String name, String value, String path, String domain, int maxAge, boolean secure, boolean httpOnly);
         OutputStream getOutputStream();
+        void sendError(int code);
         void sendError(int code, String message);
 
         /**
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientInitialAccessResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientInitialAccessResource.java
new file mode 100644
index 0000000..8875a4c
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientInitialAccessResource.java
@@ -0,0 +1,31 @@
+package org.keycloak.admin.client.resource;
+
+import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
+import org.keycloak.representations.idm.ClientInitialAccessPresentation;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface ClientInitialAccessResource {
+
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    ClientInitialAccessPresentation create(ClientInitialAccessCreatePresentation rep);
+    
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    List<ClientInitialAccessPresentation> list();
+
+    @DELETE
+    @Path("{id}")
+    void delete(final @PathParam("id") String id);
+
+}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/GroupResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/GroupResource.java
new file mode 100755
index 0000000..84af76e
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/GroupResource.java
@@ -0,0 +1,80 @@
+package org.keycloak.admin.client.resource;
+
+import org.jboss.resteasy.annotations.cache.NoCache;
+import org.keycloak.representations.idm.GroupRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface GroupResource {
+
+    /**
+     * Does not expand hierarchy.  Subgroups will not be set.
+     *
+     * @return
+     */
+    @GET
+    @NoCache
+    @Produces(MediaType.APPLICATION_JSON)
+    public GroupRepresentation toRepresentation();
+
+    /**
+     * Update group
+     *
+     * @param rep
+     */
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void update(GroupRepresentation rep);
+
+    @DELETE
+    public void remove();
+
+
+    /**
+     * Set or create child.  This will just set the parent if it exists.  Create it and set the parent
+     * if the group doesn't exist.
+     *
+     * @param rep
+     */
+    @POST
+    @Path("children")
+    @NoCache
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response subGroup(GroupRepresentation rep);
+
+
+    @Path("role-mappings")
+    public RoleMappingResource roles();
+
+    /**
+     * Get users
+     * <p/>
+     * Returns a list of users, filtered according to query parameters
+     *
+     * @param firstResult Pagination offset
+     * @param maxResults  Pagination size
+     * @return
+     */
+    @GET
+    @NoCache
+    @Path("/members")
+    @Produces(MediaType.APPLICATION_JSON)
+    public List<UserRepresentation> members(@QueryParam("first") Integer firstResult,
+                                            @QueryParam("max") Integer maxResults);
+}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/GroupsResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/GroupsResource.java
new file mode 100755
index 0000000..f917d49
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/GroupsResource.java
@@ -0,0 +1,39 @@
+package org.keycloak.admin.client.resource;
+
+import org.jboss.resteasy.annotations.cache.NoCache;
+import org.keycloak.representations.idm.GroupRepresentation;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface GroupsResource {
+    @GET
+    @NoCache
+    @Produces(MediaType.APPLICATION_JSON)
+    public List<GroupRepresentation> groups();
+
+    /**
+     * create or add a top level realm groupSet or create child.  This will update the group and set the parent if it exists.  Create it and set the parent
+     * if the group doesn't exist.
+     *
+     * @param rep
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response add(GroupRepresentation rep);
+
+    @Path("{id}")
+    public GroupResource group(@PathParam("id") String id);
+
+}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
old mode 100644
new mode 100755
index 87513ec..6dee1cb
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
@@ -1,6 +1,8 @@
 package org.keycloak.admin.client.resource;
 
+import org.jboss.resteasy.annotations.cache.NoCache;
 import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.GroupRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 
 import javax.ws.rs.*;
@@ -36,6 +38,28 @@ public interface RealmResource {
     @Path("roles")
     RolesResource roles();
 
+    @Path("groups")
+    GroupsResource groups();
+
+    @GET
+    @Path("group-by-path/{path: .*}")
+    @NoCache
+    @Produces(MediaType.APPLICATION_JSON)
+    public GroupRepresentation getGroupByPath(@PathParam("path") String path);
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("default-groups")
+    public List<GroupRepresentation> getDefaultGroups();
+
+    @PUT
+    @Path("default-groups/{groupId}")
+    public void addDefaultGroup(@PathParam("groupId") String groupId);
+
+    @DELETE
+    @Path("default-groups/{groupId}")
+    public void removeDefaultGroup(@PathParam("groupId") String groupId);
+
     @Path("identity-provider")
     IdentityProvidersResource identityProviders();
 
@@ -45,5 +69,8 @@ public interface RealmResource {
     @Path("client-session-stats")
     @GET
     List<Map<String, String>> getClientSessionStats();
-    
+
+    @Path("clients-initial-access")
+    ClientInitialAccessResource clientInitialAccess();
+
 }
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserResource.java
index a2490b6..cc70e24 100755
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserResource.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserResource.java
@@ -2,6 +2,7 @@ package org.keycloak.admin.client.resource;
 
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.representations.idm.FederatedIdentityRepresentation;
+import org.keycloak.representations.idm.GroupRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
 import org.keycloak.representations.idm.UserSessionRepresentation;
 
@@ -34,6 +35,21 @@ public interface UserResource {
     @DELETE
     public void remove();
 
+    @Path("groups")
+    @GET
+    List<GroupRepresentation> groups();
+
+    @Path("groups/{groupId}")
+    @PUT
+    void joinGroup(@PathParam("groupId") String groupId);
+
+    @Path("groups/{groupId}")
+    @DELETE
+    void leaveGroup(@PathParam("groupId") String groupId);
+
+
+
+
     @POST
     @Path("logout")
     public void logout();
diff --git a/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsHttpFacade.java b/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsHttpFacade.java
index cce85d5..29f483b 100755
--- a/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsHttpFacade.java
+++ b/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsHttpFacade.java
@@ -133,6 +133,13 @@ public class JaxrsHttpFacade implements OIDCHttpFacade {
         }
 
         @Override
+        public void sendError(int code) {
+            javax.ws.rs.core.Response response = responseBuilder.status(code).build();
+            requestContext.abortWith(response);
+            responseFinished = true;
+        }
+
+        @Override
         public void sendError(int code, String message) {
             javax.ws.rs.core.Response response = responseBuilder.status(code).entity(message).build();
             requestContext.abortWith(response);
diff --git a/integration/jetty/jetty8.1/pom.xml b/integration/jetty/jetty8.1/pom.xml
index 580f9c0..b7577ec 100755
--- a/integration/jetty/jetty8.1/pom.xml
+++ b/integration/jetty/jetty8.1/pom.xml
@@ -70,21 +70,21 @@
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-server</artifactId>
             <version>${jetty9.version}</version>
-            <scope>compile</scope>
+            <scope>provided</scope>
         </dependency>
 
         <dependency>
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-util</artifactId>
             <version>${jetty9.version}</version>
-            <scope>compile</scope>
+            <scope>provided</scope>
         </dependency>
 
         <dependency>
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-security</artifactId>
             <version>${jetty9.version}</version>
-            <scope>compile</scope>
+            <scope>provided</scope>
         </dependency>
 
 		<dependency>
diff --git a/integration/jetty/jetty9.1/pom.xml b/integration/jetty/jetty9.1/pom.xml
index f0c4a5b..0ad77ea 100755
--- a/integration/jetty/jetty9.1/pom.xml
+++ b/integration/jetty/jetty9.1/pom.xml
@@ -81,21 +81,21 @@
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-server</artifactId>
             <version>${jetty9.version}</version>
-            <scope>compile</scope>
+            <scope>provided</scope>
         </dependency>
 
         <dependency>
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-util</artifactId>
             <version>${jetty9.version}</version>
-            <scope>compile</scope>
+            <scope>provided</scope>
         </dependency>
 
         <dependency>
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-security</artifactId>
             <version>${jetty9.version}</version>
-            <scope>compile</scope>
+            <scope>provided</scope>
         </dependency>
 
 		<dependency>
diff --git a/integration/jetty/jetty9.2/pom.xml b/integration/jetty/jetty9.2/pom.xml
index ab82620..7ae5028 100755
--- a/integration/jetty/jetty9.2/pom.xml
+++ b/integration/jetty/jetty9.2/pom.xml
@@ -67,21 +67,21 @@
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-server</artifactId>
             <version>${jetty9.version}</version>
-            <scope>compile</scope>
+            <scope>provided</scope>
         </dependency>
 
         <dependency>
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-util</artifactId>
             <version>${jetty9.version}</version>
-            <scope>compile</scope>
+            <scope>provided</scope>
         </dependency>
 
         <dependency>
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-security</artifactId>
             <version>${jetty9.version}</version>
-            <scope>compile</scope>
+            <scope>provided</scope>
         </dependency>
 
 		<dependency>
diff --git a/integration/jetty/jetty-adapter-spi/pom.xml b/integration/jetty/jetty-adapter-spi/pom.xml
index 2072ba3..40839ef 100755
--- a/integration/jetty/jetty-adapter-spi/pom.xml
+++ b/integration/jetty/jetty-adapter-spi/pom.xml
@@ -12,7 +12,7 @@
 	<artifactId>keycloak-jetty-adapter-spi</artifactId>
 	<name>Keycloak Jetty Adapter SPI</name>
     <properties>
-        <jetty9.version>8.1.16.v20140903</jetty9.version>
+        <jetty9.version>8.1.17.v20150415</jetty9.version>
         <keycloak.osgi.export>
             org.keycloak.adapters.jetty.spi.*
         </keycloak.osgi.export>
@@ -43,21 +43,21 @@
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-server</artifactId>
             <version>${jetty9.version}</version>
-            <scope>compile</scope>
+            <scope>provided</scope>
         </dependency>
 
         <dependency>
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-util</artifactId>
             <version>${jetty9.version}</version>
-            <scope>compile</scope>
+            <scope>provided</scope>
         </dependency>
 
         <dependency>
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-security</artifactId>
             <version>${jetty9.version}</version>
-            <scope>compile</scope>
+            <scope>provided</scope>
         </dependency>
 
 		<dependency>
diff --git a/integration/jetty/jetty-adapter-spi/src/main/java/org/keycloak/adapters/jetty/spi/JettyHttpFacade.java b/integration/jetty/jetty-adapter-spi/src/main/java/org/keycloak/adapters/jetty/spi/JettyHttpFacade.java
index ea2b3af..c1008fd 100755
--- a/integration/jetty/jetty-adapter-spi/src/main/java/org/keycloak/adapters/jetty/spi/JettyHttpFacade.java
+++ b/integration/jetty/jetty-adapter-spi/src/main/java/org/keycloak/adapters/jetty/spi/JettyHttpFacade.java
@@ -171,6 +171,15 @@ public class JettyHttpFacade implements HttpFacade {
         }
 
         @Override
+        public void sendError(int code) {
+            try {
+                response.sendError(code);
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        @Override
         public void sendError(int code, String message) {
             try {
                 response.sendError(code, message);
diff --git a/integration/jetty/jetty-core/pom.xml b/integration/jetty/jetty-core/pom.xml
index f1029a9..5c0700c 100755
--- a/integration/jetty/jetty-core/pom.xml
+++ b/integration/jetty/jetty-core/pom.xml
@@ -71,21 +71,21 @@
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-server</artifactId>
             <version>${jetty9.version}</version>
-            <scope>compile</scope>
+            <scope>provided</scope>
         </dependency>
 
         <dependency>
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-util</artifactId>
             <version>${jetty9.version}</version>
-            <scope>compile</scope>
+            <scope>provided</scope>
         </dependency>
 
         <dependency>
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-security</artifactId>
             <version>${jetty9.version}</version>
-            <scope>compile</scope>
+            <scope>provided</scope>
         </dependency>
 
 		<dependency>
diff --git a/integration/js/src/main/resources/keycloak.js b/integration/js/src/main/resources/keycloak.js
index 7b378de..09e0d86 100755
--- a/integration/js/src/main/resources/keycloak.js
+++ b/integration/js/src/main/resources/keycloak.js
@@ -153,23 +153,23 @@
                 + '&response_type=code';
 
             if (options && options.prompt) {
-                url += '&prompt=' + options.prompt;
+                url += '&prompt=' + encodeURIComponent(options.prompt);
             }
 
             if (options && options.loginHint) {
-                url += '&login_hint=' + options.loginHint;
+                url += '&login_hint=' + encodeURIComponent(options.loginHint);
             }
 
             if (options && options.idpHint) {
-                url += '&kc_idp_hint=' + options.idpHint;
+                url += '&kc_idp_hint=' + encodeURIComponent(options.idpHint);
             }
 
             if (options && options.scope) {
-                url += '&scope=' + options.scope;
+                url += '&scope=' + encodeURIComponent(options.scope);
             }
 
             if (options && options.locale) {
-                url += '&ui_locales=' + options.locale;
+                url += '&ui_locales=' + encodeURIComponent(options.locale);
             }
 
             return url;
diff --git a/integration/osgi-adapter/src/main/java/org/keycloak/adapters/osgi/PaxWebIntegrationService.java b/integration/osgi-adapter/src/main/java/org/keycloak/adapters/osgi/PaxWebIntegrationService.java
index 1aa67f0..3be665b 100644
--- a/integration/osgi-adapter/src/main/java/org/keycloak/adapters/osgi/PaxWebIntegrationService.java
+++ b/integration/osgi-adapter/src/main/java/org/keycloak/adapters/osgi/PaxWebIntegrationService.java
@@ -1,9 +1,9 @@
 package org.keycloak.adapters.osgi;
 
 import java.net.URL;
+import java.security.SecureRandom;
 import java.util.Arrays;
 import java.util.List;
-import java.util.Random;
 
 import org.eclipse.jetty.security.ConstraintMapping;
 import org.eclipse.jetty.util.security.Constraint;
@@ -133,7 +133,8 @@ public class PaxWebIntegrationService {
         Constraint constraint = constraintMapping.getConstraint();
         String[] roles = constraint.getRoles();
         // name property is unavailable on constraint object :/
-        String name = "Constraint-" + new Random().nextInt();
+
+        String name = "Constraint-" + new SecureRandom().nextInt(Integer.MAX_VALUE);
 
         int dataConstraint = constraint.getDataConstraint();
         String dataConstraintStr;
diff --git a/integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/FilterSessionStore.java b/integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/FilterSessionStore.java
index aad0ec5..641ca70 100755
--- a/integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/FilterSessionStore.java
+++ b/integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/FilterSessionStore.java
@@ -90,6 +90,9 @@ public class FilterSessionStore implements AdapterSessionStore {
 
                 MultivaluedHashMap<String, String> getParams() {
                     if (parameters != null) return parameters;
+
+                    if (body == null) return new MultivaluedHashMap<String, String>();
+
                     String contentType = getContentType();
                     contentType = contentType.toLowerCase();
                     if (contentType.startsWith("application/x-www-form-urlencoded")) {
diff --git a/integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/ServletHttpFacade.java b/integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/ServletHttpFacade.java
index 1550eaa..eb487f5 100755
--- a/integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/ServletHttpFacade.java
+++ b/integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/ServletHttpFacade.java
@@ -157,6 +157,15 @@ public class ServletHttpFacade implements HttpFacade {
         }
 
         @Override
+        public void sendError(int code) {
+            try {
+                response.sendError(code);
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        @Override
         public void sendError(int code, String message) {
             try {
                 response.sendError(code, message);
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/AdapterDeploymentContextBean.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/AdapterDeploymentContextBean.java
index 60d413c..a517416 100644
--- a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/AdapterDeploymentContextBean.java
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/AdapterDeploymentContextBean.java
@@ -3,10 +3,7 @@ package org.keycloak.adapters.springsecurity;
 import org.keycloak.adapters.AdapterDeploymentContext;
 import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.KeycloakDeploymentBuilder;
-import org.springframework.beans.BeansException;
 import org.springframework.beans.factory.InitializingBean;
-import org.springframework.context.ApplicationContext;
-import org.springframework.context.ApplicationContextAware;
 import org.springframework.core.io.Resource;
 
 import java.io.FileNotFoundException;
@@ -20,16 +17,17 @@ import java.io.IOException;
  * @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
  * @version $Revision: 1 $
  */
-public class AdapterDeploymentContextBean implements ApplicationContextAware, InitializingBean {
+public class AdapterDeploymentContextBean implements InitializingBean {
 
-    private static final String KEYCLOAK_CONFIG_FILE = "keycloak.json";
-    private static final String KEYCLOAK_CONFIG_WEB_RESOURCE = "WEB-INF/" + KEYCLOAK_CONFIG_FILE;
-    private static final String KEYCLOAK_CONFIG_CLASSPATH_RESOURCE = "classpath:" + KEYCLOAK_CONFIG_FILE;
+    private final Resource keycloakConfigFileResource;
 
-    private ApplicationContext applicationContext;
     private AdapterDeploymentContext deploymentContext;
     private KeycloakDeployment deployment;
 
+    public AdapterDeploymentContextBean(Resource keycloakConfigFileResource) {
+        this.keycloakConfigFileResource = keycloakConfigFileResource;
+    }
+
     @Override
     public void afterPropertiesSet() throws Exception {
         this.deployment = loadKeycloakDeployment();
@@ -38,17 +36,12 @@ public class AdapterDeploymentContextBean implements ApplicationContextAware, In
 
     private KeycloakDeployment loadKeycloakDeployment() throws IOException {
 
-        Resource resource = applicationContext.getResource(KEYCLOAK_CONFIG_WEB_RESOURCE);
-
-        if (!resource.isReadable()) {
-            resource=  applicationContext.getResource(KEYCLOAK_CONFIG_CLASSPATH_RESOURCE);
-        }
-
-        if (!resource.isReadable()) {
-            throw new FileNotFoundException(String.format("Unable to locate Keycloak from %s or %s", KEYCLOAK_CONFIG_WEB_RESOURCE, KEYCLOAK_CONFIG_CLASSPATH_RESOURCE));
+        if (!keycloakConfigFileResource.isReadable()) {
+            throw new FileNotFoundException(String.format("Unable to locate Keycloak configuration file: %s",
+                    keycloakConfigFileResource.getFilename()));
         }
 
-        return KeycloakDeploymentBuilder.build(resource.getInputStream());
+        return KeycloakDeploymentBuilder.build(keycloakConfigFileResource.getInputStream());
     }
 
     /**
@@ -68,9 +61,4 @@ public class AdapterDeploymentContextBean implements ApplicationContextAware, In
     public KeycloakDeployment getDeployment() {
         return deployment;
     }
-
-    @Override
-    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
-        this.applicationContext = applicationContext;
-    }
 }
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/config/KeycloakWebSecurityConfigurerAdapter.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/config/KeycloakWebSecurityConfigurerAdapter.java
index 6cbcbe6..b5ef665 100644
--- a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/config/KeycloakWebSecurityConfigurerAdapter.java
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/config/KeycloakWebSecurityConfigurerAdapter.java
@@ -8,7 +8,9 @@ import org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcess
 import org.keycloak.adapters.springsecurity.filter.KeycloakCsrfRequestMatcher;
 import org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter;
 import org.keycloak.adapters.springsecurity.management.HttpSessionManager;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
+import org.springframework.core.io.Resource;
 import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.builders.WebSecurity;
@@ -26,19 +28,20 @@ import org.springframework.security.web.authentication.www.BasicAuthenticationFi
  *
  * @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
  * @version $Revision: 1 $
- *
  * @see EnableWebSecurity
  * @see EnableWebMvcSecurity
  */
 public abstract class KeycloakWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter implements WebSecurityConfigurer<WebSecurity> {
 
+    @Value("${keycloak.configurationFile:WEB-INF/keycloak.json}")
+    private Resource keycloakConfigFileResource;
+
     @Bean
     protected AdapterDeploymentContextBean adapterDeploymentContextBean() {
-        return new AdapterDeploymentContextBean();
+        return new AdapterDeploymentContextBean(keycloakConfigFileResource);
     }
 
-    protected AuthenticationEntryPoint authenticationEntryPoint()
-    {
+    protected AuthenticationEntryPoint authenticationEntryPoint() {
         return new KeycloakAuthenticationEntryPoint();
     }
 
@@ -48,7 +51,7 @@ public abstract class KeycloakWebSecurityConfigurerAdapter extends WebSecurityCo
 
     @Bean
     protected KeycloakAuthenticationProcessingFilter keycloakAuthenticationProcessingFilter() throws Exception {
-        KeycloakAuthenticationProcessingFilter filter  = new KeycloakAuthenticationProcessingFilter(authenticationManagerBean());
+        KeycloakAuthenticationProcessingFilter filter = new KeycloakAuthenticationProcessingFilter(authenticationManagerBean());
         filter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy());
         return filter;
     }
@@ -64,7 +67,7 @@ public abstract class KeycloakWebSecurityConfigurerAdapter extends WebSecurityCo
 
     @Bean
     protected HttpSessionManager httpSessionManager() {
-        return  new HttpSessionManager();
+        return new HttpSessionManager();
     }
 
     protected KeycloakLogoutHandler keycloakLogoutHandler() {
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletResponse.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletResponse.java
index c6b352f..c356ebd 100644
--- a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletResponse.java
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletResponse.java
@@ -96,6 +96,15 @@ class WrappedHttpServletResponse implements Response {
     }
 
     @Override
+    public void sendError(int code) {
+        try {
+            response.sendError(code);
+        } catch (IOException e) {
+            throw new RuntimeException("Unable to set HTTP status", e);
+        }
+    }
+
+    @Override
     public void sendError(int code, String message) {
         try {
             response.sendError(code, message);
diff --git a/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/AdapterDeploymentContextBeanTest.java b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/AdapterDeploymentContextBeanTest.java
new file mode 100644
index 0000000..3510db7
--- /dev/null
+++ b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/AdapterDeploymentContextBeanTest.java
@@ -0,0 +1,56 @@
+package org.keycloak.adapters.springsecurity;
+
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+
+import java.io.FileNotFoundException;
+
+import static org.junit.Assert.assertNotNull;
+
+public class AdapterDeploymentContextBeanTest {
+
+    @Rule
+    public ExpectedException expectedException = ExpectedException.none();
+
+    private AdapterDeploymentContextBean adapterDeploymentContextBean;
+
+    @Test
+    public void should_create_deployment_and_deployment_context() throws Exception {
+
+        //given:
+        adapterDeploymentContextBean = new AdapterDeploymentContextBean(getCorrectResource());
+
+        //when:
+        adapterDeploymentContextBean.afterPropertiesSet();
+
+        //then
+        assertNotNull(adapterDeploymentContextBean.getDeployment());
+        assertNotNull(adapterDeploymentContextBean.getDeploymentContext());
+    }
+
+    private Resource getCorrectResource() {
+        return new ClassPathResource("keycloak.json");
+    }
+
+    @Test
+    public void should_throw_exception_when_configuration_file_was_not_found() throws Exception {
+
+        //given:
+        adapterDeploymentContextBean = new AdapterDeploymentContextBean(getEmptyResource());
+
+        //then:
+        expectedException.expect(FileNotFoundException.class);
+        expectedException.expectMessage("Unable to locate Keycloak configuration file: no-file.json");
+
+        //when:
+        adapterDeploymentContextBean.afterPropertiesSet();
+    }
+
+    private Resource getEmptyResource() {
+        return new ClassPathResource("no-file.json");
+    }
+}
diff --git a/integration/spring-security/src/test/resources/keycloak.json b/integration/spring-security/src/test/resources/keycloak.json
new file mode 100644
index 0000000..61e0f93
--- /dev/null
+++ b/integration/spring-security/src/test/resources/keycloak.json
@@ -0,0 +1,10 @@
+{
+    "realm": "spring-security",
+    "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCh65Gqi3BSaVe12JHlqChWm8WscICrj46MVqmRoO9FCmqbxEpCQhE1RLjW+GDyc3YdXW3xqUQ3AZxDkTmN1h6BWkhdxPLzA4EnwgWmGurhyJlUF9Id2tKns0jbC+Z7kIb2LcOiKHKL7mRb3q7EtWubNnrvunv8fx+WeXGaQoGEVQIDAQAB",
+    "auth-server-url": "http://localhost:8080/auth",
+    "ssl-required": "external",
+    "resource": "some-resource",
+    "credentials": {
+        "secret": "a9c3501e-20dd-4277-8a7b-351063848446"
+    }
+}
diff --git a/integration/tomcat/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaHttpFacade.java b/integration/tomcat/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaHttpFacade.java
index cdac621..ba0b376 100755
--- a/integration/tomcat/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaHttpFacade.java
+++ b/integration/tomcat/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaHttpFacade.java
@@ -168,6 +168,15 @@ public class CatalinaHttpFacade implements HttpFacade {
         }
 
         @Override
+        public void sendError(int code) {
+            try {
+                response.sendError(code);
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        @Override
         public void sendError(int code, String message) {
             try {
                 response.sendError(code, message);
@@ -176,6 +185,7 @@ public class CatalinaHttpFacade implements HttpFacade {
             }
         }
 
+
         @Override
         public void end() {
             ended = true;
diff --git a/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java b/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java
index 3d37877..f420533 100755
--- a/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java
+++ b/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java
@@ -171,6 +171,12 @@ public class UndertowHttpFacade implements HttpFacade {
         }
 
         @Override
+        public void sendError(int code) {
+            exchange.setResponseCode(code);
+            exchange.endExchange();
+        }
+
+        @Override
         public void sendError(int code, String message) {
             exchange.setResponseCode(code);
             exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/html");
diff --git a/model/api/src/main/java/org/keycloak/migration/MigrationModel.java b/model/api/src/main/java/org/keycloak/migration/MigrationModel.java
index beb2f98..596f278 100755
--- a/model/api/src/main/java/org/keycloak/migration/MigrationModel.java
+++ b/model/api/src/main/java/org/keycloak/migration/MigrationModel.java
@@ -11,7 +11,7 @@ public interface MigrationModel {
     /**
      * Must have the form of major.minor.micro as the version is parsed and numbers are compared
      */
-    public static final String LATEST_VERSION = "1.6.0";
+    public static final String LATEST_VERSION = "1.7.0";
 
     String getStoredVersion();
     void setStoredVersion(String version);
diff --git a/model/api/src/main/java/org/keycloak/models/ClientInitialAccessModel.java b/model/api/src/main/java/org/keycloak/models/ClientInitialAccessModel.java
new file mode 100755
index 0000000..7447319
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/ClientInitialAccessModel.java
@@ -0,0 +1,22 @@
+package org.keycloak.models;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface ClientInitialAccessModel {
+
+    String getId();
+
+    RealmModel getRealm();
+
+    int getTimestamp();
+
+    int getExpiration();
+
+    int getCount();
+
+    int getRemainingCount();
+
+    void decreaseRemainingCount();
+
+}
diff --git a/model/api/src/main/java/org/keycloak/models/ClientModel.java b/model/api/src/main/java/org/keycloak/models/ClientModel.java
index 8753f45..051493e 100755
--- a/model/api/src/main/java/org/keycloak/models/ClientModel.java
+++ b/model/api/src/main/java/org/keycloak/models/ClientModel.java
@@ -90,6 +90,9 @@ public interface ClientModel extends RoleContainerModel {
     String getSecret();
     public void setSecret(String secret);
 
+    String getRegistrationToken();
+    void setRegistrationToken(String registrationToken);
+
     boolean isFullScopeAllowed();
     void setFullScopeAllowed(boolean value);
 
diff --git a/model/api/src/main/java/org/keycloak/models/Constants.java b/model/api/src/main/java/org/keycloak/models/Constants.java
index 8977def..3ecc51c 100755
--- a/model/api/src/main/java/org/keycloak/models/Constants.java
+++ b/model/api/src/main/java/org/keycloak/models/Constants.java
@@ -23,5 +23,6 @@ public interface Constants {
     // 30 days
     int DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT = 2592000;
 
-    public static final String VERIFY_EMAIL_KEY = "VERIFY_EMAIL_KEY";
+    String VERIFY_EMAIL_KEY = "VERIFY_EMAIL_KEY";
+    String KEY = "key";
 }
diff --git a/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java b/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java
index aa4f725..d04699f 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java
@@ -17,6 +17,7 @@ public class ClientEntity extends AbstractIdentifiableEntity {
     private boolean enabled;
     private String clientAuthenticatorType;
     private String secret;
+    private String registrationToken;
     private String protocol;
     private int notBefore;
     private boolean publicClient;
@@ -90,6 +91,14 @@ public class ClientEntity extends AbstractIdentifiableEntity {
         this.secret = secret;
     }
 
+    public String getRegistrationToken() {
+        return registrationToken;
+    }
+
+    public void setRegistrationToken(String registrationToken) {
+        this.registrationToken = registrationToken;
+    }
+
     public int getNotBefore() {
         return notBefore;
     }
diff --git a/model/api/src/main/java/org/keycloak/models/entities/GroupEntity.java b/model/api/src/main/java/org/keycloak/models/entities/GroupEntity.java
new file mode 100755
index 0000000..5e64643
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/entities/GroupEntity.java
@@ -0,0 +1,60 @@
+package org.keycloak.models.entities;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bburke@redhat.com">Bill Burke/a>
+ */
+public class GroupEntity extends AbstractIdentifiableEntity {
+
+    private String name;
+    private String realmId;
+
+    private List<String> roleIds;
+    private String parentId;
+    private Map<String, List<String>> attributes;
+
+    public String getRealmId() {
+        return realmId;
+    }
+
+    public void setRealmId(String realmId) {
+        this.realmId = realmId;
+    }
+
+    public List<String> getRoleIds() {
+        return roleIds;
+    }
+
+    public void setRoleIds(List<String> roleIds) {
+        this.roleIds = roleIds;
+    }
+
+    public Map<String, List<String>> getAttributes() {
+        return attributes;
+    }
+
+    public void setAttributes(Map<String, List<String>> attributes) {
+        this.attributes = attributes;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getParentId() {
+        return parentId;
+    }
+
+    public void setParentId(String parentId) {
+        this.parentId = parentId;
+    }
+}
+
diff --git a/model/api/src/main/java/org/keycloak/models/entities/IdentityProviderEntity.java b/model/api/src/main/java/org/keycloak/models/entities/IdentityProviderEntity.java
index c4cdd62..f92f1d2 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/IdentityProviderEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/IdentityProviderEntity.java
@@ -30,11 +30,11 @@ public class IdentityProviderEntity {
     private String providerId;
     private String name;
     private boolean enabled;
-    private String updateProfileFirstLoginMode;
     private boolean trustEmail;
     private boolean storeToken;
     protected boolean addReadTokenRoleOnCreate;
     private boolean authenticateByDefault;
+    private String firstBrokerLoginFlowId;
 
     private Map<String, String> config = new HashMap<String, String>();
 
@@ -62,14 +62,6 @@ public class IdentityProviderEntity {
         this.enabled = enabled;
     }
 
-    public String getUpdateProfileFirstLoginMode() {
-        return updateProfileFirstLoginMode;
-    }
-
-    public void setUpdateProfileFirstLoginMode(String updateProfileFirstLoginMode) {
-        this.updateProfileFirstLoginMode = updateProfileFirstLoginMode;
-    }
-
     public boolean isAuthenticateByDefault() {
         return authenticateByDefault;
     }
@@ -78,6 +70,14 @@ public class IdentityProviderEntity {
         this.authenticateByDefault = authenticateByDefault;
     }
 
+    public String getFirstBrokerLoginFlowId() {
+        return firstBrokerLoginFlowId;
+    }
+
+    public void setFirstBrokerLoginFlowId(String firstBrokerLoginFlowId) {
+        this.firstBrokerLoginFlowId = firstBrokerLoginFlowId;
+    }
+
     public boolean isStoreToken() {
         return this.storeToken;
     }
diff --git a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
index 389ec0a..8f5c844 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
@@ -61,6 +61,7 @@ public class RealmEntity extends AbstractIdentifiableEntity {
 
     // We are using names of defaultRoles (not ids)
     private List<String> defaultRoles = new ArrayList<String>();
+    private List<String> defaultGroups = new ArrayList<String>();
 
     private List<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>();
     private List<UserFederationProviderEntity> userFederationProviders = new ArrayList<UserFederationProviderEntity>();
@@ -629,6 +630,14 @@ public class RealmEntity extends AbstractIdentifiableEntity {
     public void setClientAuthenticationFlow(String clientAuthenticationFlow) {
         this.clientAuthenticationFlow = clientAuthenticationFlow;
     }
+
+    public List<String> getDefaultGroups() {
+        return defaultGroups;
+    }
+
+    public void setDefaultGroups(List<String> defaultGroups) {
+        this.defaultGroups = defaultGroups;
+    }
 }
 
 
diff --git a/model/api/src/main/java/org/keycloak/models/entities/UserEntity.java b/model/api/src/main/java/org/keycloak/models/entities/UserEntity.java
index 8c82a8e..2c118be 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/UserEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/UserEntity.java
@@ -21,6 +21,7 @@ public class UserEntity extends AbstractIdentifiableEntity {
     private String realmId;
 
     private List<String> roleIds;
+    private List<String> groupIds;
 
     private Map<String, List<String>> attributes;
     private List<String> requiredActions;
@@ -157,5 +158,13 @@ public class UserEntity extends AbstractIdentifiableEntity {
     public void setServiceAccountClientLink(String serviceAccountClientLink) {
         this.serviceAccountClientLink = serviceAccountClientLink;
     }
+
+    public List<String> getGroupIds() {
+        return groupIds;
+    }
+
+    public void setGroupIds(List<String> groupIds) {
+        this.groupIds = groupIds;
+    }
 }
 
diff --git a/model/api/src/main/java/org/keycloak/models/GroupModel.java b/model/api/src/main/java/org/keycloak/models/GroupModel.java
new file mode 100755
index 0000000..27402b0
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/GroupModel.java
@@ -0,0 +1,68 @@
+package org.keycloak.models;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface GroupModel extends RoleMapperModel {
+    String getId();
+
+    String getName();
+
+    void setName(String name);
+
+    /**
+     * Set single value of specified attribute. Remove all other existing values
+     *
+     * @param name
+     * @param value
+     */
+    void setSingleAttribute(String name, String value);
+
+    void setAttribute(String name, List<String> values);
+
+    void removeAttribute(String name);
+
+    /**
+     * @param name
+     * @return null if there is not any value of specified attribute or first value otherwise. Don't throw exception if there are more values of the attribute
+     */
+    String getFirstAttribute(String name);
+
+    /**
+     * @param name
+     * @return list of all attribute values or empty list if there are not any values. Never return null
+     */
+    List<String> getAttribute(String name);
+
+    Map<String, List<String>> getAttributes();
+
+    GroupModel getParent();
+    String getParentId();
+    Set<GroupModel> getSubGroups();
+
+    /**
+     * You must also call addChild on the parent group, addChild on RealmModel if there is no parent group
+     *
+     * @param group
+     */
+    void setParent(GroupModel group);
+
+    /**
+     * Automatically calls setParent() on the subGroup
+     *
+     * @param subGroup
+     */
+    void addChild(GroupModel subGroup);
+
+    /**
+     * Automatically calls setParent() on the subGroup
+     *
+     * @param subGroup
+     */
+    void removeChild(GroupModel subGroup);
+}
diff --git a/model/api/src/main/java/org/keycloak/models/IdentityProviderModel.java b/model/api/src/main/java/org/keycloak/models/IdentityProviderModel.java
index fa3eb71..862f723 100755
--- a/model/api/src/main/java/org/keycloak/models/IdentityProviderModel.java
+++ b/model/api/src/main/java/org/keycloak/models/IdentityProviderModel.java
@@ -45,14 +45,6 @@ public class IdentityProviderModel implements Serializable {
     private String providerId;
 
     private boolean enabled;
-
-    /**
-     * For possible values see {@link IdentityProviderRepresentation#getUpdateProfileFirstLoginMode()}
-     * @see IdentityProviderRepresentation#UPFLM_ON
-     * @see IdentityProviderRepresentation#UPFLM_MISSING
-     * @see IdentityProviderRepresentation#UPFLM_OFF
-     */
-    protected String updateProfileFirstLoginMode = IdentityProviderRepresentation.UPFLM_ON;
     
     private boolean trustEmail;
 
@@ -64,6 +56,8 @@ public class IdentityProviderModel implements Serializable {
      */
     private boolean authenticateByDefault;
 
+    private String firstBrokerLoginFlowId;
+
     /**
      * <p>A map containing the configuration and properties for a specific identity provider instance and implementation. The items
      * in the map are understood by the identity provider implementation.</p>
@@ -79,11 +73,11 @@ public class IdentityProviderModel implements Serializable {
         this.alias = model.getAlias();
         this.config = new HashMap<String, String>(model.getConfig());
         this.enabled = model.isEnabled();
-        this.updateProfileFirstLoginMode = model.getUpdateProfileFirstLoginMode();
         this.trustEmail = model.isTrustEmail();
         this.storeToken = model.isStoreToken();
         this.authenticateByDefault = model.isAuthenticateByDefault();
         this.addReadTokenRoleOnCreate = model.addReadTokenRoleOnCreate;
+        this.firstBrokerLoginFlowId = model.getFirstBrokerLoginFlowId();
     }
 
     public String getInternalId() {
@@ -118,20 +112,6 @@ public class IdentityProviderModel implements Serializable {
         this.enabled = enabled;
     }
 
-    /**
-     * @see IdentityProviderRepresentation#getUpdateProfileFirstLoginMode() 
-     */
-    public String getUpdateProfileFirstLoginMode() {
-        return updateProfileFirstLoginMode;
-    }
-
-    /**
-     * @see IdentityProviderRepresentation#setUpdateProfileFirstLoginMode(String) 
-     */
-    public void setUpdateProfileFirstLoginMode(String updateProfileFirstLoginMode) {
-        this.updateProfileFirstLoginMode = updateProfileFirstLoginMode;
-    }
-
     public boolean isStoreToken() {
         return this.storeToken;
     }
@@ -148,6 +128,14 @@ public class IdentityProviderModel implements Serializable {
         this.authenticateByDefault = authenticateByDefault;
     }
 
+    public String getFirstBrokerLoginFlowId() {
+        return firstBrokerLoginFlowId;
+    }
+
+    public void setFirstBrokerLoginFlowId(String firstBrokerLoginFlowId) {
+        this.firstBrokerLoginFlowId = firstBrokerLoginFlowId;
+    }
+
     public Map<String, String> getConfig() {
         return this.config;
     }
diff --git a/model/api/src/main/java/org/keycloak/models/KeycloakContext.java b/model/api/src/main/java/org/keycloak/models/KeycloakContext.java
index cf8bc6d..f430e5f 100755
--- a/model/api/src/main/java/org/keycloak/models/KeycloakContext.java
+++ b/model/api/src/main/java/org/keycloak/models/KeycloakContext.java
@@ -5,6 +5,7 @@ import org.keycloak.models.utils.RealmImporter;
 
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.UriInfo;
+import java.net.URI;
 import java.util.Locale;
 
 /**
@@ -12,6 +13,8 @@ import java.util.Locale;
  */
 public interface KeycloakContext {
 
+    URI getAuthServerUrl();
+
     String getContextPath();
 
     UriInfo getUri();
diff --git a/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java b/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java
index aff7d37..3674334 100755
--- a/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java
+++ b/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java
@@ -76,6 +76,8 @@ public class PasswordPolicy implements Serializable {
                 list.add(new PasswordHistory(arg));
             } else if (name.equals(ForceExpiredPasswordChange.NAME)) {
                 list.add(new ForceExpiredPasswordChange(arg));
+            } else {
+                throw new IllegalArgumentException("Unsupported policy");
             }
         }
         return list;
diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index 0737ee3..62f9162 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -165,6 +165,12 @@ public interface RealmModel extends RoleContainerModel {
 
     void updateDefaultRoles(String[] defaultRoles);
 
+    List<GroupModel> getDefaultGroups();
+
+    void addDefaultGroup(GroupModel group);
+
+    void removeDefaultGroup(GroupModel group);
+
     // Key is clientId
     Map<String, ClientModel> getClientNameMap();
 
@@ -328,4 +334,20 @@ public interface RealmModel extends RoleContainerModel {
     void setSupportedLocales(Set<String> locales);
     String getDefaultLocale();
     void setDefaultLocale(String locale);
+
+    GroupModel createGroup(String name);
+    GroupModel createGroup(String id, String name);
+
+    /**
+     * Move Group to top realm level.  Basically just sets group parent to null.  You need to call this though
+     * to make sure caches are set properly
+     *
+     * @param subGroup
+     */
+    void addTopLevelGroup(GroupModel subGroup);
+    GroupModel getGroupById(String id);
+    List<GroupModel> getGroups();
+    List<GroupModel> getTopLevelGroups();
+    boolean removeGroup(GroupModel group);
+    void moveGroup(GroupModel group, GroupModel toParent);
 }
diff --git a/model/api/src/main/java/org/keycloak/models/RealmProvider.java b/model/api/src/main/java/org/keycloak/models/RealmProvider.java
index 8e864cf..f649f35 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmProvider.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmProvider.java
@@ -20,8 +20,11 @@ public interface RealmProvider extends Provider {
 
     RoleModel getRoleById(String id, RealmModel realm);
     ClientModel getClientById(String id, RealmModel realm);
+    GroupModel getGroupById(String id, RealmModel realm);
+
+
+
     List<RealmModel> getRealms();
     boolean removeRealm(String id);
-
     void close();
 }
diff --git a/model/api/src/main/java/org/keycloak/models/RoleMapperModel.java b/model/api/src/main/java/org/keycloak/models/RoleMapperModel.java
new file mode 100755
index 0000000..931f153
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/RoleMapperModel.java
@@ -0,0 +1,21 @@
+package org.keycloak.models;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface RoleMapperModel {
+    Set<RoleModel> getRealmRoleMappings();
+
+    Set<RoleModel> getClientRoleMappings(ClientModel app);
+
+    boolean hasRole(RoleModel role);
+
+    void grantRole(RoleModel role);
+
+    Set<RoleModel> getRoleMappings();
+
+    void deleteRoleMapping(RoleModel role);
+}
diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
index cde5eb9..1a1709b 100755
--- a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
@@ -166,6 +166,16 @@ public class UserFederationManager implements UserProvider {
     }
 
     @Override
+    public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
+        return session.userStorage().getGroupMembers(realm, group, firstResult, maxResults);
+    }
+
+    @Override
+    public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group) {
+        return getGroupMembers(realm, group, -1, -1);
+    }
+
+    @Override
     public UserModel getUserByUsername(String username, RealmModel realm) {
         UserModel user = session.userStorage().getUserByUsername(username.toLowerCase(), realm);
         if (user != null) {
@@ -354,6 +364,16 @@ public class UserFederationManager implements UserProvider {
     }
 
     @Override
+    public void preRemove(RealmModel realm, GroupModel group) {
+        for (UserFederationProviderModel federation : realm.getUserFederationProviders()) {
+            UserFederationProvider fed = getFederationProvider(federation);
+            fed.preRemove(realm, group);
+        }
+        session.userStorage().preRemove(realm, group);
+
+    }
+
+    @Override
     public void preRemove(RealmModel realm, RoleModel role) {
         for (UserFederationProviderModel federation : realm.getUserFederationProviders()) {
             UserFederationProvider fed = getFederationProvider(federation);
@@ -442,6 +462,9 @@ public class UserFederationManager implements UserProvider {
     }
 
 
+
+
+
     @Override
     public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) {
         return validCredentials(realm, user, Arrays.asList(input));
diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationProvider.java b/model/api/src/main/java/org/keycloak/models/UserFederationProvider.java
index f60c21d..043176e 100755
--- a/model/api/src/main/java/org/keycloak/models/UserFederationProvider.java
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationProvider.java
@@ -120,6 +120,14 @@ public interface UserFederationProvider extends Provider {
     void preRemove(RealmModel realm, RoleModel role);
 
     /**
+     * called before a role is removed.
+     *
+     * @param realm
+     * @param group
+     */
+    void preRemove(RealmModel realm, GroupModel group);
+
+    /**
      * Is the Keycloak UserModel still valid and/or existing in federated storage?  Keycloak may call this method
      * in various user operations.  The local storage may be deleted if this method returns false.
      *
diff --git a/model/api/src/main/java/org/keycloak/models/UserModel.java b/model/api/src/main/java/org/keycloak/models/UserModel.java
index 9dec778..a36f88b 100755
--- a/model/api/src/main/java/org/keycloak/models/UserModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserModel.java
@@ -8,7 +8,7 @@ import java.util.Set;
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
-public interface UserModel {
+public interface UserModel extends RoleMapperModel {
     String USERNAME = "username";
     String LAST_NAME = "lastName";
     String FIRST_NAME = "firstName";
@@ -94,12 +94,10 @@ public interface UserModel {
 
     void updateCredentialDirectly(UserCredentialValueModel cred);
 
-    Set<RoleModel> getRealmRoleMappings();
-    Set<RoleModel> getClientRoleMappings(ClientModel app);
-    boolean hasRole(RoleModel role);
-    void grantRole(RoleModel role);
-    Set<RoleModel> getRoleMappings();
-    void deleteRoleMapping(RoleModel role);
+    Set<GroupModel> getGroups();
+    void joinGroup(GroupModel group);
+    void leaveGroup(GroupModel group);
+    boolean isMemberOf(GroupModel group);
 
     String getFederationLink();
     void setFederationLink(String link);
diff --git a/model/api/src/main/java/org/keycloak/models/UserProvider.java b/model/api/src/main/java/org/keycloak/models/UserProvider.java
index 82be2fe..962bc77 100755
--- a/model/api/src/main/java/org/keycloak/models/UserProvider.java
+++ b/model/api/src/main/java/org/keycloak/models/UserProvider.java
@@ -25,12 +25,16 @@ public interface UserProvider extends Provider {
     UserModel getUserById(String id, RealmModel realm);
     UserModel getUserByUsername(String username, RealmModel realm);
     UserModel getUserByEmail(String email, RealmModel realm);
+
+    List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults);
+
     UserModel getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm);
     UserModel getUserByServiceAccountClient(ClientModel client);
     List<UserModel> getUsers(RealmModel realm, boolean includeServiceAccounts);
 
     // Service account is included for counts
     int getUsersCount(RealmModel realm);
+    List<UserModel> getGroupMembers(RealmModel realm, GroupModel group);
     List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts);
     List<UserModel> searchForUser(String search, RealmModel realm);
     List<UserModel> searchForUser(String search, RealmModel realm, int firstResult, int maxResults);
@@ -50,6 +54,7 @@ public interface UserProvider extends Provider {
     void preRemove(RealmModel realm, UserFederationProviderModel link);
 
     void preRemove(RealmModel realm, RoleModel role);
+    void preRemove(RealmModel realm, GroupModel group);
 
     void preRemove(RealmModel realm, ClientModel client);
     void preRemove(ClientModel realm, ProtocolMapperModel protocolMapper);
diff --git a/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java b/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java
index 1a59f4f..0c1df9c 100755
--- a/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java
+++ b/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java
@@ -63,6 +63,11 @@ public interface UserSessionProvider extends Provider {
     UserSessionModel importUserSession(UserSessionModel persistentUserSession, boolean offline);
     ClientSessionModel importClientSession(ClientSessionModel persistentClientSession, boolean offline);
 
+    ClientInitialAccessModel createClientInitialAccessModel(RealmModel realm, int expiration, int count);
+    ClientInitialAccessModel getClientInitialAccessModel(RealmModel realm, String id);
+    void removeClientInitialAccessModel(RealmModel realm, String id);
+    List<ClientInitialAccessModel> listClientInitialAccess(RealmModel realm);
+
     void close();
 
 }
diff --git a/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java b/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java
index 7ce15d8..af9b6b5 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java
@@ -51,6 +51,10 @@ public class CredentialValidation {
     }
 
     public static boolean validateHashedCredential(RealmModel realm, UserModel user, String unhashedCredValue, UserCredentialValueModel credential) {
+        if(unhashedCredValue == null){
+            return false;
+        }
+
         boolean validated = new Pbkdf2PasswordEncoder(credential.getSalt()).verify(unhashedCredValue, credential.getValue(), credential.getHashIterations());
         if (validated) {
             int iterations = hashIterations(realm);
diff --git a/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
index 9992af3..40a8999 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
@@ -1,9 +1,16 @@
 package org.keycloak.models.utils;
 
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
 import org.keycloak.models.AuthenticationExecutionModel;
 import org.keycloak.models.AuthenticationFlowModel;
+import org.keycloak.models.AuthenticatorConfigModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredCredentialModel;
+import org.keycloak.representations.idm.IdentityProviderRepresentation;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -19,6 +26,11 @@ public class DefaultAuthenticationFlows {
     public static final String LOGIN_FORMS_FLOW = "forms";
 
     public static final String CLIENT_AUTHENTICATION_FLOW = "clients";
+    public static final String FIRST_BROKER_LOGIN_FLOW = "first broker login";
+    public static final String FIRST_BROKER_LOGIN_HANDLE_EXISTING_SUBFLOW = "Handle Existing Account";
+
+    public static final String IDP_REVIEW_PROFILE_CONFIG_ALIAS = "review profile config";
+    public static final String IDP_CREATE_UNIQUE_USER_CONFIG_ALIAS = "create unique user config";
 
     public static void addFlows(RealmModel realm) {
         if (realm.getFlowByAlias(BROWSER_FLOW) == null) browserFlow(realm);
@@ -26,6 +38,7 @@ public class DefaultAuthenticationFlows {
         if (realm.getFlowByAlias(REGISTRATION_FLOW) == null) registrationFlow(realm);
         if (realm.getFlowByAlias(RESET_CREDENTIALS_FLOW) == null) resetCredentialsFlow(realm);
         if (realm.getFlowByAlias(CLIENT_AUTHENTICATION_FLOW) == null) clientAuthFlow(realm);
+        if (realm.getFlowByAlias(FIRST_BROKER_LOGIN_FLOW) == null) firstBrokerLoginFlow(realm, false);
     }
     public static void migrateFlows(RealmModel realm) {
         if (realm.getFlowByAlias(BROWSER_FLOW) == null) browserFlow(realm, true);
@@ -33,6 +46,7 @@ public class DefaultAuthenticationFlows {
         if (realm.getFlowByAlias(REGISTRATION_FLOW) == null) registrationFlow(realm);
         if (realm.getFlowByAlias(RESET_CREDENTIALS_FLOW) == null) resetCredentialsFlow(realm);
         if (realm.getFlowByAlias(CLIENT_AUTHENTICATION_FLOW) == null) clientAuthFlow(realm);
+        if (realm.getFlowByAlias(FIRST_BROKER_LOGIN_FLOW) == null) firstBrokerLoginFlow(realm, true);
     }
 
     public static void registrationFlow(RealmModel realm) {
@@ -309,4 +323,124 @@ public class DefaultAuthenticationFlows {
         execution.setAuthenticatorFlow(false);
         realm.addAuthenticatorExecution(execution);
     }
+
+    public static void firstBrokerLoginFlow(RealmModel realm, boolean migrate) {
+        AuthenticationFlowModel firstBrokerLogin = new AuthenticationFlowModel();
+        firstBrokerLogin.setAlias(FIRST_BROKER_LOGIN_FLOW);
+        firstBrokerLogin.setDescription("Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account");
+        firstBrokerLogin.setProviderId("basic-flow");
+        firstBrokerLogin.setTopLevel(true);
+        firstBrokerLogin.setBuiltIn(true);
+        firstBrokerLogin = realm.addAuthenticationFlow(firstBrokerLogin);
+
+        AuthenticatorConfigModel reviewProfileConfig = new AuthenticatorConfigModel();
+        reviewProfileConfig.setAlias(IDP_REVIEW_PROFILE_CONFIG_ALIAS);
+        Map<String, String> config = new HashMap<>();
+        config.put("update.profile.on.first.login", IdentityProviderRepresentation.UPFLM_MISSING);
+        reviewProfileConfig.setConfig(config);
+        reviewProfileConfig = realm.addAuthenticatorConfig(reviewProfileConfig);
+
+        AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
+        execution.setParentFlow(firstBrokerLogin.getId());
+        execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
+        execution.setAuthenticator("idp-review-profile");
+        execution.setPriority(10);
+        execution.setAuthenticatorFlow(false);
+        execution.setAuthenticatorConfig(reviewProfileConfig.getId());
+        realm.addAuthenticatorExecution(execution);
+
+
+        AuthenticatorConfigModel createUserIfUniqueConfig = new AuthenticatorConfigModel();
+        createUserIfUniqueConfig.setAlias(IDP_CREATE_UNIQUE_USER_CONFIG_ALIAS);
+        config = new HashMap<>();
+        config.put("require.password.update.after.registration", "false");
+        createUserIfUniqueConfig.setConfig(config);
+        createUserIfUniqueConfig = realm.addAuthenticatorConfig(createUserIfUniqueConfig);
+
+        execution = new AuthenticationExecutionModel();
+        execution.setParentFlow(firstBrokerLogin.getId());
+        execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
+        execution.setAuthenticator("idp-create-user-if-unique");
+        execution.setPriority(20);
+        execution.setAuthenticatorFlow(false);
+        execution.setAuthenticatorConfig(createUserIfUniqueConfig.getId());
+        realm.addAuthenticatorExecution(execution);
+
+
+        AuthenticationFlowModel linkExistingAccountFlow = new AuthenticationFlowModel();
+        linkExistingAccountFlow.setTopLevel(false);
+        linkExistingAccountFlow.setBuiltIn(true);
+        linkExistingAccountFlow.setAlias(FIRST_BROKER_LOGIN_HANDLE_EXISTING_SUBFLOW);
+        linkExistingAccountFlow.setDescription("Handle what to do if there is existing account with same email/username like authenticated identity provider");
+        linkExistingAccountFlow.setProviderId("basic-flow");
+        linkExistingAccountFlow = realm.addAuthenticationFlow(linkExistingAccountFlow);
+        execution = new AuthenticationExecutionModel();
+        execution.setParentFlow(firstBrokerLogin.getId());
+        execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
+        execution.setFlowId(linkExistingAccountFlow.getId());
+        execution.setPriority(30);
+        execution.setAuthenticatorFlow(true);
+        realm.addAuthenticatorExecution(execution);
+
+        execution = new AuthenticationExecutionModel();
+        execution.setParentFlow(linkExistingAccountFlow.getId());
+        execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
+        execution.setAuthenticator("idp-confirm-link");
+        execution.setPriority(10);
+        execution.setAuthenticatorFlow(false);
+        realm.addAuthenticatorExecution(execution);
+
+        execution = new AuthenticationExecutionModel();
+        execution.setParentFlow(linkExistingAccountFlow.getId());
+        execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
+        execution.setAuthenticator("idp-email-verification");
+        execution.setPriority(20);
+        execution.setAuthenticatorFlow(false);
+        realm.addAuthenticatorExecution(execution);
+
+        AuthenticationFlowModel verifyByReauthenticationAccountFlow = new AuthenticationFlowModel();
+        verifyByReauthenticationAccountFlow.setTopLevel(false);
+        verifyByReauthenticationAccountFlow.setBuiltIn(true);
+        verifyByReauthenticationAccountFlow.setAlias("Verify Existing Account by Re-authentication");
+        verifyByReauthenticationAccountFlow.setDescription("Reauthentication of existing account");
+        verifyByReauthenticationAccountFlow.setProviderId("basic-flow");
+        verifyByReauthenticationAccountFlow = realm.addAuthenticationFlow(verifyByReauthenticationAccountFlow);
+        execution = new AuthenticationExecutionModel();
+        execution.setParentFlow(linkExistingAccountFlow.getId());
+        execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
+        execution.setFlowId(verifyByReauthenticationAccountFlow.getId());
+        execution.setPriority(30);
+        execution.setAuthenticatorFlow(true);
+        realm.addAuthenticatorExecution(execution);
+
+        // password + otp
+        execution = new AuthenticationExecutionModel();
+        execution.setParentFlow(verifyByReauthenticationAccountFlow.getId());
+        execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
+        execution.setAuthenticator("idp-username-password-form");
+        execution.setPriority(10);
+        execution.setAuthenticatorFlow(false);
+        realm.addAuthenticatorExecution(execution);
+
+        execution = new AuthenticationExecutionModel();
+        execution.setParentFlow(verifyByReauthenticationAccountFlow.getId());
+        execution.setRequirement(AuthenticationExecutionModel.Requirement.OPTIONAL);
+
+        if (migrate) {
+            // Try to read OTP requirement from browser flow
+            AuthenticationFlowModel browserFlow = realm.getBrowserFlow();
+            List<AuthenticationExecutionModel> browserExecutions = new LinkedList<>();
+            KeycloakModelUtils.deepFindAuthenticationExecutions(realm, browserFlow, browserExecutions);
+            for (AuthenticationExecutionModel browserExecution : browserExecutions) {
+                if (browserExecution.getAuthenticator().equals("auth-otp-form")) {
+                    execution.setRequirement(browserExecution.getRequirement());
+                }
+            }
+        }
+
+        execution.setAuthenticator("auth-otp-form");
+        execution.setPriority(20);
+        execution.setAuthenticatorFlow(false);
+        realm.addAuthenticatorExecution(execution);
+    }
 }
diff --git a/model/api/src/main/java/org/keycloak/models/utils/FormMessage.java b/model/api/src/main/java/org/keycloak/models/utils/FormMessage.java
index b840de6..9f5c5de 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/FormMessage.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/FormMessage.java
@@ -23,6 +23,9 @@ public class FormMessage {
 	private String message;
 	private Object[] parameters;
 
+	public FormMessage() {
+	}
+
 	/**
 	 * Create message.
 	 * 
diff --git a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
index 4b630d8..4126daa 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
@@ -1,8 +1,13 @@
 package org.keycloak.models.utils;
 
 import org.bouncycastle.openssl.PEMWriter;
+import org.keycloak.common.util.Base64Url;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.AuthenticationFlowModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.Constants;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.KeycloakSessionTask;
@@ -14,6 +19,7 @@ import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserFederationMapperModel;
 import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.UserModel;
+import org.keycloak.representations.idm.AuthenticationExecutionRepresentation;
 import org.keycloak.representations.idm.CertificateRepresentation;
 import org.keycloak.common.util.CertificateUtils;
 import org.keycloak.common.util.PemUtils;
@@ -27,8 +33,11 @@ import java.security.KeyPairGenerator;
 import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
 import java.security.PublicKey;
+import java.security.SecureRandom;
 import java.security.cert.X509Certificate;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -172,9 +181,9 @@ public final class KeycloakModelUtils {
         return rep;
     }
 
-    public static UserCredentialModel generateSecret(ClientModel app) {
+    public static UserCredentialModel generateSecret(ClientModel client) {
         UserCredentialModel secret = UserCredentialModel.generateSecret();
-        app.setSecret(secret.getValue());
+        client.setSecret(secret.getValue());
         return secret;
     }
 
@@ -198,9 +207,9 @@ public final class KeycloakModelUtils {
     /**
      * Deep search if given role is descendant of composite role
      *
-     * @param role role to check
+     * @param role      role to check
      * @param composite composite role
-     * @param visited set of already visited roles (used for recursion)
+     * @param visited   set of already visited roles (used for recursion)
      * @return true if "role" is descendant of "composite"
      */
     public static boolean searchFor(RoleModel role, RoleModel composite, Set<RoleModel> visited) {
@@ -218,14 +227,14 @@ public final class KeycloakModelUtils {
     /**
      * Try to find user by given username. If it fails, then fallback to find him by email
      *
-     * @param realm realm
+     * @param realm    realm
      * @param username username or email of user
      * @return found user
      */
     public static UserModel findUserByNameOrEmail(KeycloakSession session, RealmModel realm, String username) {
         UserModel user = session.users().getUserByUsername(username, realm);
         if (user == null && username.contains("@")) {
-            user =  session.users().getUserByEmail(username, realm);
+            user = session.users().getUserByEmail(username, realm);
         }
         return user;
     }
@@ -265,7 +274,6 @@ public final class KeycloakModelUtils {
     }
 
     /**
-     *
      * @param roles
      * @param targetRole
      * @return true if targetRole is in roles (directly or indirectly via composite role)
@@ -279,13 +287,31 @@ public final class KeycloakModelUtils {
         return false;
     }
 
+    /**
+     *
+     * @param groups
+     * @param targetGroup
+     * @return true if targetGroup is in groups (directly or indirectly via parent child relationship)
+     */
+    public static boolean isMember(Set<GroupModel> groups, GroupModel targetGroup) {
+        if (groups.contains(targetGroup)) return true;
+
+        for (GroupModel mapping : groups) {
+            GroupModel child = mapping;
+            while(child.getParent() != null) {
+                if (child.getParent().equals(targetGroup)) return true;
+                child = child.getParent();
+            }
+        }
+        return false;
+    }
     // USER FEDERATION RELATED STUFF
 
     /**
      * Ensure that displayName of myProvider (if not null) is unique and there is no other provider with same displayName in the list.
      *
-     * @param displayName to check for duplications
-     * @param myProvider provider, which is excluded from the list (if present)
+     * @param displayName         to check for duplications
+     * @param myProvider          provider, which is excluded from the list (if present)
      * @param federationProviders
      * @throws ModelDuplicateException if there is other provider with same displayName
      */
@@ -367,4 +393,121 @@ public final class KeycloakModelUtils {
             realm.addDefaultRole(Constants.OFFLINE_ACCESS_ROLE);
         }
     }
+
+
+    /**
+     * Recursively find all AuthenticationExecutionModel from specified flow or all it's subflows
+     *
+     * @param realm
+     * @param flow
+     * @param result input should be empty list. At the end will be all executions added to this list
+     */
+    public static void deepFindAuthenticationExecutions(RealmModel realm, AuthenticationFlowModel flow, List<AuthenticationExecutionModel> result) {
+        List<AuthenticationExecutionModel> executions = realm.getAuthenticationExecutions(flow.getId());
+        for (AuthenticationExecutionModel execution : executions) {
+            if (execution.isAuthenticatorFlow()) {
+                AuthenticationFlowModel subFlow = realm.getAuthenticationFlowById(execution.getFlowId());
+                deepFindAuthenticationExecutions(realm, subFlow, result);
+            } else {
+                result.add(execution);
+            }
+        }
+    }
+
+    public static String resolveFirstAttribute(GroupModel group, String name) {
+        String value = group.getFirstAttribute(name);
+        if (value != null) return value;
+        if (group.getParentId() == null) return null;
+        return resolveFirstAttribute(group.getParent(), name);
+
+    }
+
+    /**
+     *
+     *
+     * @param user
+     * @param name
+     * @return
+     */
+    public static String resolveFirstAttribute(UserModel user, String name) {
+        String value = user.getFirstAttribute(name);
+        if (value != null) return value;
+        for (GroupModel group : user.getGroups()) {
+            value = resolveFirstAttribute(group, name);
+            if (value != null) return value;
+        }
+        return null;
+
+    }
+
+    public static List<String>  resolveAttribute(GroupModel group, String name) {
+        List<String> values = group.getAttribute(name);
+        if (values != null && !values.isEmpty()) return values;
+        if (group.getParentId() == null) return null;
+        return resolveAttribute(group.getParent(), name);
+
+    }
+
+
+    public static List<String> resolveAttribute(UserModel user, String name) {
+        List<String> values = user.getAttribute(name);
+        if (!values.isEmpty()) return values;
+        for (GroupModel group : user.getGroups()) {
+            values = resolveAttribute(group, name);
+            if (values != null) return values;
+        }
+        return Collections.emptyList();
+    }
+
+
+    private static GroupModel findSubGroup(String[] path, int index, GroupModel parent) {
+        for (GroupModel group : parent.getSubGroups()) {
+            if (group.getName().equals(path[index])) {
+                if (path.length == index + 1) {
+                    return group;
+                }
+                else {
+                    if (index + 1 < path.length) {
+                        GroupModel found = findSubGroup(path, index + 1, group);
+                        if (found != null) return found;
+                    } else {
+                        return null;
+                    }
+                }
+
+            }
+        }
+        return null;
+    }
+
+    public static GroupModel findGroupByPath(RealmModel realm, String path) {
+        if (path == null) {
+            return null;
+        }
+        if (path.startsWith("/")) {
+            path = path.substring(1);
+        }
+        if (path.endsWith("/")) {
+            path = path.substring(0, path.length() - 1);
+        }
+        String[] split = path.split("/");
+        if (split.length == 0) return null;
+        GroupModel found = null;
+        for (GroupModel group : realm.getTopLevelGroups()) {
+            if (group.getName().equals(split[0])) {
+                if (split.length == 1) {
+                    found = group;
+                    break;
+                }
+                else {
+                    if (split.length > 1) {
+                        found = findSubGroup(split, 1, group);
+                        if (found != null) break;
+                    }
+                }
+
+            }
+        }
+        return found;
+    }
 }
diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index 5fcb826..723617b 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -6,12 +6,11 @@ import org.keycloak.models.AuthenticatorConfigModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.FederatedIdentityModel;
+import org.keycloak.models.GroupModel;
 import org.keycloak.models.IdentityProviderMapperModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.ModelException;
 import org.keycloak.models.OTPPolicy;
-import org.keycloak.models.session.PersistentClientSessionModel;
-import org.keycloak.models.session.PersistentUserSessionModel;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredActionProviderModel;
@@ -30,6 +29,7 @@ import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
 import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.representations.idm.FederatedIdentityRepresentation;
+import org.keycloak.representations.idm.GroupRepresentation;
 import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
 import org.keycloak.representations.idm.IdentityProviderRepresentation;
 import org.keycloak.representations.idm.ProtocolMapperRepresentation;
@@ -44,20 +44,81 @@ import org.keycloak.representations.idm.UserRepresentation;
 import org.keycloak.representations.idm.UserSessionRepresentation;
 import org.keycloak.common.util.Time;
 
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
 public class ModelToRepresentation {
+    public static void buildGroupPath(StringBuilder sb, GroupModel group) {
+        if (group.getParent() != null) {
+            buildGroupPath(sb, group.getParent());
+        }
+        sb.append('/').append(group.getName());
+    }
+
+    public static String buildGroupPath(GroupModel group) {
+        StringBuilder sb = new StringBuilder();
+        buildGroupPath(sb, group);
+        return sb.toString();
+    }
+
+
+    public static GroupRepresentation toRepresentation(GroupModel group, boolean full) {
+        GroupRepresentation rep = new GroupRepresentation();
+        rep.setId(group.getId());
+        rep.setName(group.getName());
+        rep.setPath(buildGroupPath(group));
+        if (!full) return rep;
+        // Role mappings
+        Set<RoleModel> roles = group.getRoleMappings();
+        List<String> realmRoleNames = new ArrayList<>();
+        Map<String, List<String>> clientRoleNames = new HashMap<>();
+        for (RoleModel role : roles) {
+            if (role.getContainer() instanceof RealmModel) {
+                realmRoleNames.add(role.getName());
+            } else {
+                ClientModel client = (ClientModel)role.getContainer();
+                String clientId = client.getClientId();
+                List<String> currentClientRoles = clientRoleNames.get(clientId);
+                if (currentClientRoles == null) {
+                    currentClientRoles = new ArrayList<>();
+                    clientRoleNames.put(clientId, currentClientRoles);
+                }
+
+                currentClientRoles.add(role.getName());
+            }
+        }
+        rep.setRealmRoles(realmRoleNames);
+        rep.setClientRoles(clientRoleNames);
+        Map<String, List<String>> attributes = group.getAttributes();
+        rep.setAttributes(attributes);
+        return rep;
+    }
+
+    public static List<GroupRepresentation> toGroupHierarchy(RealmModel realm, boolean full) {
+        List<GroupRepresentation> hierarchy = new LinkedList<>();
+        List<GroupModel> groups = realm.getTopLevelGroups();
+        if (groups == null) return hierarchy;
+        for (GroupModel group : groups) {
+            GroupRepresentation rep = toGroupHierarchy(group, full);
+            hierarchy.add(rep);
+        }
+        return hierarchy;
+    }
+
+    public static GroupRepresentation toGroupHierarchy(GroupModel group, boolean full) {
+        GroupRepresentation rep = toRepresentation(group, full);
+        List<GroupRepresentation> subGroups = new LinkedList<>();
+        for (GroupModel subGroup : group.getSubGroups()) {
+            subGroups.add(toGroupHierarchy(subGroup, full));
+        }
+        rep.setSubGroups(subGroups);
+        return rep;
+    }
+
+
     public static UserRepresentation toRepresentation(UserModel user) {
         UserRepresentation rep = new UserRepresentation();
         rep.setId(user.getId());
@@ -178,6 +239,14 @@ public class ModelToRepresentation {
             roleStrings.addAll(defaultRoles);
             rep.setDefaultRoles(roleStrings);
         }
+        List<GroupModel> defaultGroups = realm.getDefaultGroups();
+        if (!defaultGroups.isEmpty()) {
+            List<String> groupPaths = new LinkedList<>();
+            for (GroupModel group : defaultGroups) {
+                groupPaths.add(ModelToRepresentation.buildGroupPath(group));
+            }
+            rep.setDefaultGroups(groupPaths);
+        }
 
         List<RequiredCredentialModel> requiredCredentialModels = realm.getRequiredCredentials();
         if (requiredCredentialModels.size() > 0) {
@@ -202,7 +271,7 @@ public class ModelToRepresentation {
         }
 
         for (IdentityProviderModel provider : realm.getIdentityProviders()) {
-            rep.addIdentityProvider(toRepresentation(provider));
+            rep.addIdentityProvider(toRepresentation(realm, provider));
         }
 
         for (IdentityProviderMapperModel mapper : realm.getIdentityProviderMappers()) {
@@ -218,26 +287,63 @@ public class ModelToRepresentation {
         if (internal) {
             exportAuthenticationFlows(realm, rep);
             exportRequiredActions(realm, rep);
+            exportGroups(realm, rep);
         }
         return rep;
     }
 
+    public static void exportGroups(RealmModel realm, RealmRepresentation rep) {
+        List<GroupRepresentation> groups = toGroupHierarchy(realm, true);
+        rep.setGroups(groups);
+    }
+
     public static void exportAuthenticationFlows(RealmModel realm, RealmRepresentation rep) {
         rep.setAuthenticationFlows(new LinkedList<AuthenticationFlowRepresentation>());
         rep.setAuthenticatorConfig(new LinkedList<AuthenticatorConfigRepresentation>());
-        for (AuthenticationFlowModel model : realm.getAuthenticationFlows()) {
+
+        List<AuthenticationFlowModel> authenticationFlows = new ArrayList<>(realm.getAuthenticationFlows());
+        //ensure consistent ordering of authenticationFlows.
+        Collections.sort(authenticationFlows, new Comparator<AuthenticationFlowModel>() {
+            @Override
+            public int compare(AuthenticationFlowModel left, AuthenticationFlowModel right) {
+                return left.getAlias().compareTo(right.getAlias());
+            }
+        });
+
+        for (AuthenticationFlowModel model : authenticationFlows) {
             AuthenticationFlowRepresentation flowRep = toRepresentation(realm, model);
             rep.getAuthenticationFlows().add(flowRep);
         }
-        for (AuthenticatorConfigModel model : realm.getAuthenticatorConfigs()) {
+
+        List<AuthenticatorConfigModel> authenticatorConfigs = new ArrayList<>(realm.getAuthenticatorConfigs());
+        //ensure consistent ordering of authenticatorConfigs.
+        Collections.sort(authenticatorConfigs, new Comparator<AuthenticatorConfigModel>() {
+            @Override
+            public int compare(AuthenticatorConfigModel left, AuthenticatorConfigModel right) {
+                return left.getAlias().compareTo(right.getAlias());
+            }
+        });
+
+        for (AuthenticatorConfigModel model : authenticatorConfigs) {
             rep.getAuthenticatorConfig().add(toRepresentation(model));
         }
 
     }
 
     public static void exportRequiredActions(RealmModel realm, RealmRepresentation rep) {
+
         rep.setRequiredActions(new LinkedList<RequiredActionProviderRepresentation>());
-        for (RequiredActionProviderModel model : realm.getRequiredActionProviders()) {
+
+        List<RequiredActionProviderModel> requiredActionProviders = realm.getRequiredActionProviders();
+        //ensure consistent ordering of requiredActionProviders.
+        Collections.sort(requiredActionProviders, new Comparator<RequiredActionProviderModel>() {
+            @Override
+            public int compare(RequiredActionProviderModel left, RequiredActionProviderModel right) {
+                return left.getAlias().compareTo(right.getAlias());
+            }
+        });
+
+        for (RequiredActionProviderModel model : requiredActionProviders) {
             RequiredActionProviderRepresentation action = toRepresentation(model);
             rep.getRequiredActions().add(action);
         }
@@ -381,7 +487,7 @@ public class ModelToRepresentation {
         return rep;
     }
 
-    public static IdentityProviderRepresentation toRepresentation(IdentityProviderModel identityProviderModel) {
+    public static IdentityProviderRepresentation toRepresentation(RealmModel realm, IdentityProviderModel identityProviderModel) {
         IdentityProviderRepresentation providerRep = new IdentityProviderRepresentation();
 
         providerRep.setInternalId(identityProviderModel.getInternalId());
@@ -389,12 +495,20 @@ public class ModelToRepresentation {
         providerRep.setAlias(identityProviderModel.getAlias());
         providerRep.setEnabled(identityProviderModel.isEnabled());
         providerRep.setStoreToken(identityProviderModel.isStoreToken());
-        providerRep.setUpdateProfileFirstLoginMode(identityProviderModel.getUpdateProfileFirstLoginMode());
         providerRep.setTrustEmail(identityProviderModel.isTrustEmail());
         providerRep.setAuthenticateByDefault(identityProviderModel.isAuthenticateByDefault());
         providerRep.setConfig(identityProviderModel.getConfig());
         providerRep.setAddReadTokenRoleOnCreate(identityProviderModel.isAddReadTokenRoleOnCreate());
 
+        String firstBrokerLoginFlowId = identityProviderModel.getFirstBrokerLoginFlowId();
+        if (firstBrokerLoginFlowId != null) {
+            AuthenticationFlowModel flow = realm.getAuthenticationFlowById(firstBrokerLoginFlowId);
+            if (flow == null) {
+                throw new ModelException("Couldn't find authentication flow with id " + firstBrokerLoginFlowId);
+            }
+            providerRep.setFirstBrokerLoginFlowAlias(flow.getAlias());
+        }
+
         return providerRep;
     }
 
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index 85b4ac0..3916fd1 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -12,6 +12,7 @@ import org.keycloak.models.BrowserSecurityHeaders;
 import org.keycloak.models.ClaimMask;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.FederatedIdentityModel;
+import org.keycloak.models.GroupModel;
 import org.keycloak.models.IdentityProviderMapperModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.KeycloakSession;
@@ -36,6 +37,7 @@ import org.keycloak.representations.idm.ClaimRepresentation;
 import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.representations.idm.FederatedIdentityRepresentation;
+import org.keycloak.representations.idm.GroupRepresentation;
 import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
 import org.keycloak.representations.idm.IdentityProviderRepresentation;
 import org.keycloak.representations.idm.OAuthClientRepresentation;
@@ -67,12 +69,12 @@ public class RepresentationToModel {
     private static Logger logger = Logger.getLogger(RepresentationToModel.class);
     public static OTPPolicy toPolicy(RealmRepresentation rep) {
         OTPPolicy policy = new OTPPolicy();
-        policy.setType(rep.getOtpPolicyType());
-        policy.setLookAheadWindow(rep.getOtpPolicyLookAheadWindow());
-        policy.setInitialCounter(rep.getOtpPolicyInitialCounter());
-        policy.setAlgorithm(rep.getOtpPolicyAlgorithm());
-        policy.setDigits(rep.getOtpPolicyDigits());
-        policy.setPeriod(rep.getOtpPolicyPeriod());
+        if (rep.getOtpPolicyType() != null) policy.setType(rep.getOtpPolicyType());
+        if (rep.getOtpPolicyLookAheadWindow() != null) policy.setLookAheadWindow(rep.getOtpPolicyLookAheadWindow());
+        if (rep.getOtpPolicyInitialCounter() != null) policy.setInitialCounter(rep.getOtpPolicyInitialCounter());
+        if (rep.getOtpPolicyAlgorithm() != null) policy.setAlgorithm(rep.getOtpPolicyAlgorithm());
+        if (rep.getOtpPolicyDigits() != null) policy.setDigits(rep.getOtpPolicyDigits());
+        if (rep.getOtpPolicyPeriod() != null) policy.setPeriod(rep.getOtpPolicyPeriod());
         return policy;
 
     }
@@ -164,6 +166,16 @@ public class RepresentationToModel {
         if (rep.getOtpPolicyType() != null) newRealm.setOTPPolicy(toPolicy(rep));
         else newRealm.setOTPPolicy(OTPPolicy.DEFAULT_POLICY);
 
+        importAuthenticationFlows(newRealm, rep);
+        if (rep.getRequiredActions() != null) {
+            for (RequiredActionProviderRepresentation action : rep.getRequiredActions()) {
+                RequiredActionProviderModel model = toModel(action);
+                newRealm.addRequiredActionProvider(model);
+            }
+        } else {
+            DefaultRequiredActions.addActions(newRealm);
+        }
+
         importIdentityProviders(rep, newRealm);
         importIdentityProviderMappers(rep, newRealm);
 
@@ -301,6 +313,18 @@ public class RepresentationToModel {
             }
         }
 
+        if (rep.getGroups() != null) {
+            importGroups(newRealm, rep);
+            if (rep.getDefaultGroups() != null) {
+                for (String path : rep.getDefaultGroups()) {
+                    GroupModel found = KeycloakModelUtils.findGroupByPath(newRealm, path);
+                    if (found == null) throw new RuntimeException("default group in realm rep doesn't exist: " + path);
+                    newRealm.addDefaultGroup(found);
+                }
+            }
+        }
+
+
         // create users and their role mappings and social mappings
 
         if (rep.getUsers() != null) {
@@ -318,15 +342,58 @@ public class RepresentationToModel {
         if(rep.getDefaultLocale() != null){
             newRealm.setDefaultLocale(rep.getDefaultLocale());
         }
+    }
 
-        importAuthenticationFlows(newRealm, rep);
-        if (rep.getRequiredActions() != null) {
-            for (RequiredActionProviderRepresentation action : rep.getRequiredActions()) {
-                RequiredActionProviderModel model = toModel(action);
-                newRealm.addRequiredActionProvider(model);
+    public static void importGroups(RealmModel realm, RealmRepresentation rep) {
+        List<GroupRepresentation> groups = rep.getGroups();
+        if (groups == null) return;
+
+        Map<String, ClientModel> clientMap = realm.getClientNameMap();
+        GroupModel parent = null;
+        for (GroupRepresentation group : groups) {
+            importGroup(realm, clientMap, parent, group);
+        }
+    }
+
+    public static void importGroup(RealmModel realm, Map<String, ClientModel> clientMap, GroupModel parent, GroupRepresentation group) {
+        GroupModel newGroup = realm.createGroup(group.getId(), group.getName());
+        if (group.getAttributes() != null) {
+            for (Map.Entry<String, List<String>> attr : group.getAttributes().entrySet()) {
+                newGroup.setAttribute(attr.getKey(), attr.getValue());
+            }
+        }
+        realm.moveGroup(newGroup, parent);
+
+        if (group.getRealmRoles() != null) {
+            for (String roleString : group.getRealmRoles()) {
+                RoleModel role = realm.getRole(roleString.trim());
+                if (role == null) {
+                    role = realm.addRole(roleString.trim());
+                }
+                newGroup.grantRole(role);
+            }
+        }
+        if (group.getClientRoles() != null) {
+            for (Map.Entry<String, List<String>> entry : group.getClientRoles().entrySet()) {
+                ClientModel client = clientMap.get(entry.getKey());
+                if (client == null) {
+                    throw new RuntimeException("Unable to find client role mappings for client: " + entry.getKey());
+                }
+                List<String> roleNames = entry.getValue();
+                for (String roleName : roleNames) {
+                    RoleModel role = client.getRole(roleName.trim());
+                    if (role == null) {
+                        role = client.addRole(roleName.trim());
+                    }
+                    newGroup.grantRole(role);
+
+                }
+            }
+        }
+        if (group.getSubGroups() != null) {
+            for (GroupRepresentation subGroup : group.getSubGroups()) {
+                importGroup(realm, clientMap, newGroup, subGroup);
             }
-        } else {
-            DefaultRequiredActions.addActions(newRealm);
         }
     }
 
@@ -999,6 +1066,16 @@ public class RepresentationToModel {
             }
             user.setServiceAccountClientLink(client.getId());;
         }
+        if (userRep.getGroups() != null) {
+            for (String path : userRep.getGroups()) {
+                GroupModel group = KeycloakModelUtils.findGroupByPath(newRealm, path);
+                if (group == null) {
+                    throw new RuntimeException("Unable to find group specified by path: " + path);
+
+                }
+                user.joinGroup(group);
+            }
+        }
         return user;
     }
 
@@ -1062,7 +1139,7 @@ public class RepresentationToModel {
     private static void importIdentityProviders(RealmRepresentation rep, RealmModel newRealm) {
         if (rep.getIdentityProviders() != null) {
             for (IdentityProviderRepresentation representation : rep.getIdentityProviders()) {
-                newRealm.addIdentityProvider(toModel(representation));
+                newRealm.addIdentityProvider(toModel(newRealm, representation));
             }
         }
     }
@@ -1073,21 +1150,31 @@ public class RepresentationToModel {
             }
         }
     }
-   public static IdentityProviderModel toModel(IdentityProviderRepresentation representation) {
+   public static IdentityProviderModel toModel(RealmModel realm, IdentityProviderRepresentation representation) {
         IdentityProviderModel identityProviderModel = new IdentityProviderModel();
 
         identityProviderModel.setInternalId(representation.getInternalId());
         identityProviderModel.setAlias(representation.getAlias());
         identityProviderModel.setProviderId(representation.getProviderId());
         identityProviderModel.setEnabled(representation.isEnabled());
-        identityProviderModel.setUpdateProfileFirstLoginMode(representation.getUpdateProfileFirstLoginMode());
         identityProviderModel.setTrustEmail(representation.isTrustEmail());
         identityProviderModel.setAuthenticateByDefault(representation.isAuthenticateByDefault());
         identityProviderModel.setStoreToken(representation.isStoreToken());
         identityProviderModel.setAddReadTokenRoleOnCreate(representation.isAddReadTokenRoleOnCreate());
         identityProviderModel.setConfig(representation.getConfig());
 
-        return identityProviderModel;
+        String flowAlias = representation.getFirstBrokerLoginFlowAlias();
+        if (flowAlias == null) {
+            flowAlias = DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW;
+        }
+
+       AuthenticationFlowModel flowModel = realm.getFlowByAlias(flowAlias);
+       if (flowModel == null) {
+           throw new ModelException("No available authentication flow with alias: " + flowAlias);
+       }
+       identityProviderModel.setFirstBrokerLoginFlowId(flowModel.getId());
+
+       return identityProviderModel;
     }
 
     public static ProtocolMapperModel toModel(ProtocolMapperRepresentation rep) {
diff --git a/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java b/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
index 4cd162b..23ca7f6 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
@@ -1,6 +1,7 @@
 package org.keycloak.models.utils;
 
 import org.keycloak.models.ClientModel;
+import org.keycloak.models.GroupModel;
 import org.keycloak.models.UserConsentModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserCredentialModel;
@@ -255,4 +256,26 @@ public class UserModelDelegate implements UserModel {
     public void setCreatedTimestamp(Long timestamp){
         delegate.setCreatedTimestamp(timestamp);
     }
+
+    @Override
+    public Set<GroupModel> getGroups() {
+        return delegate.getGroups();
+    }
+
+    @Override
+    public void joinGroup(GroupModel group) {
+        delegate.joinGroup(group);
+
+    }
+
+    @Override
+    public void leaveGroup(GroupModel group) {
+        delegate.leaveGroup(group);
+
+    }
+
+    @Override
+    public boolean isMemberOf(GroupModel group) {
+        return delegate.isMemberOf(group);
+    }
 }
diff --git a/model/api/src/main/java/org/keycloak/provider/ServerInfoAwareProviderFactory.java b/model/api/src/main/java/org/keycloak/provider/ServerInfoAwareProviderFactory.java
index 97d17b1..7674047 100644
--- a/model/api/src/main/java/org/keycloak/provider/ServerInfoAwareProviderFactory.java
+++ b/model/api/src/main/java/org/keycloak/provider/ServerInfoAwareProviderFactory.java
@@ -3,15 +3,15 @@ package org.keycloak.provider;
 import java.util.Map;
 
 /**
- * Marker interface for ProviderFactory of Provider which wants to show some info on "Server Info" page in Admin console.
+ * Marker interface for {@link ProviderFactory} of Provider which wants to show some info on "Server Info" page in Admin console.
  * 
  * @author Vlastimil Elias (velias at redhat dot com)
  */
-public interface ServerInfoAwareProviderFactory<T extends Provider> extends ProviderFactory<T> {
+public interface ServerInfoAwareProviderFactory {
 
     /**
-     * Get operational info about given provider. This info contains informations about providers configuration and operational conditions (eg. errors in connection to remote systems etc) which is
-     * shown on "Server Info" page.
+     * Return actual info about the provider. This info contains informations about providers configuration and operational conditions (eg. errors in connection to remote systems etc) which is
+     * shown on "Server Info" page then.
      * 
      * @return Map with keys describing value and relevant values itself
      */
diff --git a/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java b/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java
index df76588..8c662fb 100755
--- a/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java
+++ b/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java
@@ -83,6 +83,15 @@ public class PasswordPolicyTest {
         Assert.assertEquals("invalidPasswordNotUsernameMessage", policy.validate("jdoe", "jdoe").getMessage());
         Assert.assertNull(policy.validate("jdoe", "ab&d1234"));
     }
+
+    @Test
+    public void testInvalidPolicyName() {
+        try {
+            PasswordPolicy policy = new PasswordPolicy("noSuchPolicy");
+            Assert.fail("Expected exception");
+        } catch (IllegalArgumentException e) {
+        }
+    }
     
     @Test
     public void testRegexPatterns() {
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
index 477c5d6..e03452a 100755
--- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
@@ -120,6 +120,15 @@ public class ClientAdapter implements ClientModel {
         getDelegateForUpdate();
         updated.setSecret(secret);
     }
+    public String getRegistrationToken() {
+        if (updated != null) return updated.getRegistrationToken();
+        return cached.getRegistrationToken();
+    }
+
+    public void setRegistrationToken(String registrationToken) {
+        getDelegateForUpdate();
+        updated.setRegistrationToken(registrationToken);
+    }
 
     public boolean isPublicClient() {
         if (updated != null) return updated.isPublicClient();
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheRealmProvider.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheRealmProvider.java
old mode 100644
new mode 100755
index b84079f..3e45a69
--- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheRealmProvider.java
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheRealmProvider.java
@@ -21,9 +21,11 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
     protected Set<String> realmInvalidations = new HashSet<String>();
     protected Set<String> appInvalidations = new HashSet<String>();
     protected Set<String> roleInvalidations = new HashSet<String>();
+    protected Set<String> groupInvalidations = new HashSet<String>();
     protected Map<String, RealmModel> managedRealms = new HashMap<String, RealmModel>();
     protected Map<String, ClientModel> managedApplications = new HashMap<String, ClientModel>();
     protected Map<String, RoleModel> managedRoles = new HashMap<String, RoleModel>();
+    protected Map<String, GroupModel> managedGroups = new HashMap<String, GroupModel>();
 
     protected boolean clearAll;
 
@@ -73,6 +75,12 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
         roleInvalidations.add(id);
     }
 
+    @Override
+    public void registerGroupInvalidation(String id) {
+        groupInvalidations.add(id);
+
+    }
+
     protected void runInvalidations() {
         for (String id : realmInvalidations) {
             cache.invalidateCachedRealmById(id);
@@ -80,6 +88,9 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
         for (String id : roleInvalidations) {
             cache.invalidateRoleById(id);
         }
+        for (String id : groupInvalidations) {
+            cache.invalidateGroupById(id);
+        }
         for (String id : appInvalidations) {
             cache.invalidateCachedApplicationById(id);
         }
@@ -255,6 +266,31 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
     }
 
     @Override
+    public GroupModel getGroupById(String id, RealmModel realm) {
+        if (!cache.isEnabled()) return getDelegate().getGroupById(id, realm);
+        CachedGroup cached = cache.getGroup(id);
+        if (cached != null && !cached.getRealm().equals(realm.getId())) {
+            cached = null;
+        }
+
+        if (cached == null) {
+            GroupModel model = getDelegate().getGroupById(id, realm);
+            if (model == null) return null;
+            if (groupInvalidations.contains(id)) return model;
+            cached = new CachedGroup(realm, model);
+            cache.addCachedGroup(cached);
+
+        } else if (groupInvalidations.contains(id)) {
+            return getDelegate().getGroupById(id, realm);
+        } else if (managedGroups.containsKey(id)) {
+            return managedGroups.get(id);
+        }
+        GroupAdapter adapter = new GroupAdapter(cached, this, session, realm);
+        managedGroups.put(id, adapter);
+        return adapter;
+    }
+
+    @Override
     public ClientModel getClientById(String id, RealmModel realm) {
         if (!cache.isEnabled()) return getDelegate().getClientById(id, realm);
         CachedClient cached = cache.getApplication(id);
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheUserProvider.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheUserProvider.java
old mode 100644
new mode 100755
index 9045f91..44260a1
--- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheUserProvider.java
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheUserProvider.java
@@ -198,6 +198,16 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
     }
 
     @Override
+    public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
+        return getDelegate().getGroupMembers(realm, group, firstResult, maxResults);
+    }
+
+    @Override
+    public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group) {
+        return getDelegate().getGroupMembers(realm, group);
+    }
+
+    @Override
     public UserModel getUserByServiceAccountClient(ClientModel client) {
         return getDelegate().getUserByServiceAccountClient(client);
     }
@@ -319,6 +329,11 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
     public void preRemove(RealmModel realm, RoleModel role) {
         getDelegate().preRemove(realm, role);
     }
+    @Override
+    public void preRemove(RealmModel realm, GroupModel group) {
+        getDelegate().preRemove(realm, group);
+    }
+
 
     @Override
     public void preRemove(RealmModel realm, UserFederationProviderModel link) {
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java
new file mode 100755
index 0000000..ec6e743
--- /dev/null
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java
@@ -0,0 +1,244 @@
+package org.keycloak.models.cache.infinispan;
+
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleContainerModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.cache.CacheRealmProvider;
+import org.keycloak.models.cache.CacheUserProvider;
+import org.keycloak.models.cache.entities.CachedGroup;
+import org.keycloak.models.cache.entities.CachedUser;
+
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class GroupAdapter implements GroupModel {
+    protected GroupModel updated;
+    protected CachedGroup cached;
+    protected CacheRealmProvider cacheSession;
+    protected KeycloakSession keycloakSession;
+    protected RealmModel realm;
+
+    public GroupAdapter(CachedGroup cached, CacheRealmProvider cacheSession, KeycloakSession keycloakSession, RealmModel realm) {
+        this.cached = cached;
+        this.cacheSession = cacheSession;
+        this.keycloakSession = keycloakSession;
+        this.realm = realm;
+    }
+
+    protected void getDelegateForUpdate() {
+        if (updated == null) {
+            cacheSession.registerGroupInvalidation(getId());
+            updated = cacheSession.getDelegate().getGroupById(getId(), realm);
+            if (updated == null) throw new IllegalStateException("Not found in database");
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || !(o instanceof GroupModel)) return false;
+
+        GroupModel that = (GroupModel) o;
+
+        if (!cached.getId().equals(that.getId())) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return cached.getId().hashCode();
+    }
+
+    @Override
+    public String getId() {
+        if (updated != null) return updated.getId();
+        return cached.getId();
+    }
+
+    @Override
+    public String getName() {
+        if (updated != null) return updated.getName();
+        return cached.getName();
+    }
+
+    @Override
+    public void setName(String name) {
+        getDelegateForUpdate();
+        updated.setName(name);
+
+    }
+
+    @Override
+    public void setSingleAttribute(String name, String value) {
+        getDelegateForUpdate();
+        updated.setSingleAttribute(name, value);
+    }
+
+    @Override
+    public void setAttribute(String name, List<String> values) {
+        getDelegateForUpdate();
+        updated.setAttribute(name, values);
+    }
+
+    @Override
+    public void removeAttribute(String name) {
+        getDelegateForUpdate();
+        updated.removeAttribute(name);
+
+    }
+
+    @Override
+    public String getFirstAttribute(String name) {
+        if (updated != null) return updated.getFirstAttribute(name);
+        return cached.getAttributes().getFirst(name);
+    }
+
+    @Override
+    public List<String> getAttribute(String name) {
+        List<String> values = cached.getAttributes().get(name);
+        if (values == null) return null;
+        return values;
+    }
+
+    @Override
+    public Map<String, List<String>> getAttributes() {
+        return cached.getAttributes();
+    }
+
+    @Override
+    public Set<RoleModel> getRealmRoleMappings() {
+        if (updated != null) return updated.getRealmRoleMappings();
+        Set<RoleModel> roleMappings = getRoleMappings();
+        Set<RoleModel> realmMappings = new HashSet<RoleModel>();
+        for (RoleModel role : roleMappings) {
+            RoleContainerModel container = role.getContainer();
+            if (container instanceof RealmModel) {
+                if (((RealmModel) container).getId().equals(realm.getId())) {
+                    realmMappings.add(role);
+                }
+            }
+        }
+        return realmMappings;
+    }
+
+    @Override
+    public Set<RoleModel> getClientRoleMappings(ClientModel app) {
+        if (updated != null) return updated.getClientRoleMappings(app);
+        Set<RoleModel> roleMappings = getRoleMappings();
+        Set<RoleModel> appMappings = new HashSet<RoleModel>();
+        for (RoleModel role : roleMappings) {
+            RoleContainerModel container = role.getContainer();
+            if (container instanceof ClientModel) {
+                if (((ClientModel) container).getId().equals(app.getId())) {
+                    appMappings.add(role);
+                }
+            }
+        }
+        return appMappings;
+    }
+
+    @Override
+    public boolean hasRole(RoleModel role) {
+        if (updated != null) return updated.hasRole(role);
+        if (cached.getRoleMappings().contains(role.getId())) return true;
+
+        Set<RoleModel> mappings = getRoleMappings();
+        for (RoleModel mapping: mappings) {
+            if (mapping.hasRole(role)) return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void grantRole(RoleModel role) {
+        getDelegateForUpdate();
+        updated.grantRole(role);
+    }
+
+    @Override
+    public Set<RoleModel> getRoleMappings() {
+        if (updated != null) return updated.getRoleMappings();
+        Set<RoleModel> roles = new HashSet<RoleModel>();
+        for (String id : cached.getRoleMappings()) {
+            RoleModel roleById = keycloakSession.realms().getRoleById(id, realm);
+            if (roleById == null) {
+                // chance that role was removed, so just delegate to persistence and get user invalidated
+                getDelegateForUpdate();
+                return updated.getRoleMappings();
+            }
+            roles.add(roleById);
+
+        }
+        return roles;
+    }
+
+    @Override
+    public void deleteRoleMapping(RoleModel role) {
+        getDelegateForUpdate();
+        updated.deleteRoleMapping(role);
+    }
+
+    @Override
+    public GroupModel getParent() {
+        if (updated != null) return updated.getParent();
+        if (cached.getParentId() == null) return null;
+        return keycloakSession.realms().getGroupById(cached.getParentId(), realm);
+    }
+
+    @Override
+    public String getParentId() {
+        if (updated != null) return updated.getParentId();
+        return cached.getParentId();
+    }
+
+    @Override
+    public Set<GroupModel> getSubGroups() {
+        if (updated != null) return updated.getSubGroups();
+        Set<GroupModel> subGroups = new HashSet<>();
+        for (String id : cached.getSubGroups()) {
+            GroupModel subGroup = keycloakSession.realms().getGroupById(id, realm);
+            if (subGroup == null) {
+                // chance that role was removed, so just delegate to persistence and get user invalidated
+                getDelegateForUpdate();
+                return updated.getSubGroups();
+
+            }
+            subGroups.add(subGroup);
+        }
+        return subGroups;
+    }
+
+
+
+    @Override
+    public void setParent(GroupModel group) {
+        getDelegateForUpdate();
+        updated.setParent(group);
+
+    }
+
+    @Override
+    public void addChild(GroupModel subGroup) {
+        getDelegateForUpdate();
+        updated.addChild(subGroup);
+
+    }
+
+    @Override
+    public void removeChild(GroupModel subGroup) {
+        getDelegateForUpdate();
+        updated.removeChild(subGroup);
+    }
+}
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanRealmCache.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanRealmCache.java
index 07554a8..a5239fb 100755
--- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanRealmCache.java
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanRealmCache.java
@@ -4,6 +4,7 @@ import org.infinispan.Cache;
 import org.jboss.logging.Logger;
 import org.keycloak.models.cache.RealmCache;
 import org.keycloak.models.cache.entities.CachedClient;
+import org.keycloak.models.cache.entities.CachedGroup;
 import org.keycloak.models.cache.entities.CachedRealm;
 import org.keycloak.models.cache.entities.CachedRole;
 
@@ -102,11 +103,48 @@ public class InfinispanRealmCache implements RealmCache {
     }
 
     @Override
+    public CachedGroup getGroup(String id) {
+        if (!enabled) return null;
+        return get(id, CachedGroup.class);
+    }
+
+    @Override
+    public void invalidateGroup(CachedGroup role) {
+        logger.tracev("Removing group {0}", role.getId());
+        cache.remove(role.getId());
+
+    }
+
+    @Override
+    public void addCachedGroup(CachedGroup role) {
+        if (!enabled) return;
+        logger.tracev("Adding group {0}", role.getId());
+        cache.put(role.getId(), role);
+
+    }
+
+    @Override
+    public void invalidateCachedGroupById(String id) {
+        logger.tracev("Removing group {0}", id);
+        cache.remove(id);
+
+    }
+
+    @Override
+    public void invalidateGroupById(String id) {
+        logger.tracev("Removing group {0}", id);
+        cache.remove(id);
+
+    }
+
+    @Override
     public CachedRole getRole(String id) {
         if (!enabled) return null;
         return get(id, CachedRole.class);
     }
 
+
+
     @Override
     public void invalidateRole(CachedRole role) {
         logger.tracev("Removing role {0}", role.getId());
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
index 13ec945..02abe5e 100755
--- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
@@ -481,6 +481,30 @@ public class RealmAdapter implements RealmModel {
      }
 
     @Override
+    public List<GroupModel> getDefaultGroups() {
+        List<GroupModel> defaultGroups = new LinkedList<>();
+        for (String id : cached.getDefaultGroups()) {
+            defaultGroups.add(cacheSession.getGroupById(id, this));
+        }
+        return defaultGroups;
+
+    }
+
+    @Override
+    public void addDefaultGroup(GroupModel group) {
+        getDelegateForUpdate();
+        updated.addDefaultGroup(group);
+
+    }
+
+    @Override
+    public void removeDefaultGroup(GroupModel group) {
+        getDelegateForUpdate();
+        updated.removeDefaultGroup(group);
+
+    }
+
+    @Override
     public List<String> getDefaultRoles() {
         if (updated != null) return updated.getDefaultRoles();
         return cached.getDefaultRoles();
@@ -1262,4 +1286,67 @@ public class RealmAdapter implements RealmModel {
         if (updated != null) return updated.getRequiredActionProviderByAlias(alias);
         return cached.getRequiredActionProvidersByAlias().get(alias);
     }
+
+    @Override
+    public GroupModel getGroupById(String id) {
+        if (updated != null) return updated.getGroupById(id);
+        return cacheSession.getGroupById(id, this);
+    }
+
+    @Override
+    public List<GroupModel> getGroups() {
+        if (updated != null) return updated.getGroups();
+        if (cached.getGroups().isEmpty()) return Collections.EMPTY_LIST;
+        List<GroupModel> list = new LinkedList<>();
+        for (String id : cached.getGroups()) {
+            GroupModel group = cacheSession.getGroupById(id, this);
+            if (group == null) continue;
+            list.add(group);
+        }
+        return list;
+    }
+
+    @Override
+    public List<GroupModel> getTopLevelGroups() {
+        List<GroupModel> all = getGroups();
+        Iterator<GroupModel> it = all.iterator();
+        while (it.hasNext()) {
+            GroupModel group = it.next();
+            if (group.getParent() != null) {
+                it.remove();
+            }
+        }
+        return all;
+    }
+
+    @Override
+    public boolean removeGroup(GroupModel group) {
+        getDelegateForUpdate();
+        return updated.removeGroup(group);
+    }
+
+    @Override
+    public GroupModel createGroup(String name) {
+        getDelegateForUpdate();
+        return updated.createGroup(name);
+    }
+
+    @Override
+    public GroupModel createGroup(String id, String name) {
+        getDelegateForUpdate();
+        return updated.createGroup(id, name);
+    }
+
+    @Override
+    public void addTopLevelGroup(GroupModel subGroup) {
+        getDelegateForUpdate();
+        updated.addTopLevelGroup(subGroup);
+
+    }
+
+    @Override
+    public void moveGroup(GroupModel group, GroupModel toParent) {
+        getDelegateForUpdate();
+        updated.moveGroup(group, toParent);
+    }
 }
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
index 5a74b01..8240005 100755
--- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
@@ -318,6 +318,44 @@ public class UserAdapter implements UserModel {
     }
 
     @Override
+    public Set<GroupModel> getGroups() {
+        if (updated != null) return updated.getGroups();
+        Set<GroupModel> groups = new HashSet<GroupModel>();
+        for (String id : cached.getGroups()) {
+            GroupModel groupModel = keycloakSession.realms().getGroupById(id, realm);
+            if (groupModel == null) {
+                // chance that role was removed, so just delete to persistence and get user invalidated
+                getDelegateForUpdate();
+                return updated.getGroups();
+            }
+            groups.add(groupModel);
+
+        }
+        return groups;
+    }
+
+    @Override
+    public void joinGroup(GroupModel group) {
+        getDelegateForUpdate();
+        updated.joinGroup(group);
+
+    }
+
+    @Override
+    public void leaveGroup(GroupModel group) {
+        getDelegateForUpdate();
+        updated.leaveGroup(group);
+    }
+
+    @Override
+    public boolean isMemberOf(GroupModel group) {
+        if (updated != null) return updated.isMemberOf(group);
+        if (cached.getGroups().contains(group.getId())) return true;
+        Set<GroupModel> roles = getGroups();
+        return KeycloakModelUtils.isMember(roles, group);
+    }
+
+    @Override
     public void addConsent(UserConsentModel consent) {
         getDelegateForUpdate();
         updated.addConsent(consent);
@@ -348,4 +386,5 @@ public class UserAdapter implements UserModel {
         getDelegateForUpdate();
         return updated.revokeConsentForClient(clientId);
     }
+
 }
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/CacheRealmProvider.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/CacheRealmProvider.java
index 4aab371..26ad12d 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/CacheRealmProvider.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/CacheRealmProvider.java
@@ -17,4 +17,6 @@ public interface CacheRealmProvider extends RealmProvider {
     void registerApplicationInvalidation(String id);
 
     void registerRoleInvalidation(String id);
+
+    void registerGroupInvalidation(String id);
 }
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
index 2d58222..7c7b97d 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
@@ -31,6 +31,7 @@ public class CachedClient implements Serializable {
     private boolean enabled;
     private String clientAuthenticatorType;
     private String secret;
+    private String registrationToken;
     private String protocol;
     private Map<String, String> attributes = new HashMap<String, String>();
     private boolean publicClient;
@@ -57,6 +58,7 @@ public class CachedClient implements Serializable {
         id = model.getId();
         clientAuthenticatorType = model.getClientAuthenticatorType();
         secret = model.getSecret();
+        registrationToken = model.getRegistrationToken();
         clientId = model.getClientId();
         name = model.getName();
         description = model.getDescription();
@@ -129,6 +131,10 @@ public class CachedClient implements Serializable {
         return secret;
     }
 
+    public String getRegistrationToken() {
+        return registrationToken;
+    }
+
     public boolean isPublicClient() {
         return publicClient;
     }
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedGroup.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedGroup.java
new file mode 100755
index 0000000..a410c1b
--- /dev/null
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedGroup.java
@@ -0,0 +1,74 @@
+package org.keycloak.models.cache.entities;
+
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.models.UserModel;
+
+import java.io.Serializable;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class CachedGroup implements Serializable {
+    private String id;
+    private String realm;
+    private String name;
+    private String parentId;
+    private MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
+    private Set<String> roleMappings = new HashSet<>();
+    private Set<String> subGroups = new HashSet<>();
+
+    public CachedGroup(RealmModel realm, GroupModel group) {
+        this.id = group.getId();
+        this.realm = realm.getId();
+        this.name = group.getName();
+        this.parentId = group.getParentId();
+
+        this.attributes.putAll(group.getAttributes());
+        for (RoleModel role : group.getRoleMappings()) {
+            roleMappings.add(role.getId());
+        }
+        Set<GroupModel> subGroups1 = group.getSubGroups();
+        if (subGroups1 != null) {
+            for (GroupModel subGroup : subGroups1) {
+                subGroups.add(subGroup.getId());
+            }
+        }
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public String getRealm() {
+        return realm;
+    }
+
+    public MultivaluedHashMap<String, String> getAttributes() {
+        return attributes;
+    }
+
+    public Set<String> getRoleMappings() {
+        return roleMappings;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getParentId() {
+        return parentId;
+    }
+
+    public Set<String> getSubGroups() {
+        return subGroups;
+    }
+}
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
index 18b8540..4ae07fd 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
@@ -5,6 +5,7 @@ import org.keycloak.models.AuthenticationExecutionModel;
 import org.keycloak.models.AuthenticationFlowModel;
 import org.keycloak.models.AuthenticatorConfigModel;
 import org.keycloak.models.ClientModel;
+import org.keycloak.models.GroupModel;
 import org.keycloak.models.IdentityProviderMapperModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.OTPPolicy;
@@ -106,6 +107,8 @@ public class CachedRealm implements Serializable {
     protected Set<String> adminEnabledEventOperations = new HashSet<String>();
     protected boolean adminEventsDetailsEnabled;
     private List<String> defaultRoles = new LinkedList<String>();
+    private List<String> defaultGroups = new LinkedList<String>();
+    private Set<String> groups = new HashSet<String>();
     private Map<String, String> realmRoles = new HashMap<String, String>();
     private Map<String, String> clients = new HashMap<String, String>();
     private boolean internationalizationEnabled;
@@ -216,6 +219,9 @@ public class CachedRealm implements Serializable {
                 executionsById.put(execution.getId(), execution);
             }
         }
+        for (GroupModel group : model.getGroups()) {
+            groups.add(group.getId());
+        }
         for (AuthenticatorConfigModel authenticator : model.getAuthenticatorConfigs()) {
             authenticatorConfigs.put(authenticator.getId(), authenticator);
         }
@@ -224,6 +230,10 @@ public class CachedRealm implements Serializable {
             requiredActionProvidersByAlias.put(action.getAlias(), action);
         }
 
+        for (GroupModel group : model.getDefaultGroups()) {
+            defaultGroups.add(group.getId());
+        }
+
         browserFlow = model.getBrowserFlow();
         registrationFlow = model.getRegistrationFlow();
         directGrantFlow = model.getDirectGrantFlow();
@@ -507,4 +517,12 @@ public class CachedRealm implements Serializable {
     public AuthenticationFlowModel getClientAuthenticationFlow() {
         return clientAuthenticationFlow;
     }
+
+    public Set<String> getGroups() {
+        return groups;
+    }
+
+    public List<String> getDefaultGroups() {
+        return defaultGroups;
+    }
 }
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java
index 0b5fc78..fcd36c1 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java
@@ -1,5 +1,6 @@
 package org.keycloak.models.cache.entities;
 
+import org.keycloak.models.GroupModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserCredentialValueModel;
@@ -33,6 +34,7 @@ public class CachedUser implements Serializable {
     private MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
     private Set<String> requiredActions = new HashSet<>();
     private Set<String> roleMappings = new HashSet<>();
+    private Set<String> groups = new HashSet<>();
 
     public CachedUser(RealmModel realm, UserModel user) {
         this.id = user.getId();
@@ -53,6 +55,12 @@ public class CachedUser implements Serializable {
         for (RoleModel role : user.getRoleMappings()) {
             roleMappings.add(role.getId());
         }
+        Set<GroupModel> groupMappings = user.getGroups();
+        if (groupMappings != null) {
+            for (GroupModel group : groupMappings) {
+                groups.add(group.getId());
+            }
+        }
     }
 
     public String getId() {
@@ -118,4 +126,8 @@ public class CachedUser implements Serializable {
     public String getServiceAccountClientLink() {
         return serviceAccountClientLink;
     }
+
+    public Set<String> getGroups() {
+        return groups;
+    }
 }
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmCache.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmCache.java
index 9c895c3..df74b53 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmCache.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmCache.java
@@ -1,6 +1,7 @@
 package org.keycloak.models.cache;
 
 import org.keycloak.models.cache.entities.CachedClient;
+import org.keycloak.models.cache.entities.CachedGroup;
 import org.keycloak.models.cache.entities.CachedRealm;
 import org.keycloak.models.cache.entities.CachedRole;
 
@@ -39,6 +40,16 @@ public interface RealmCache {
 
     void invalidateRoleById(String id);
 
+    CachedGroup getGroup(String id);
+
+    void invalidateGroup(CachedGroup role);
+
+    void addCachedGroup(CachedGroup role);
+
+    void invalidateCachedGroupById(String id);
+
+    void invalidateGroupById(String id);
+
     boolean isEnabled();
 
     void setEnabled(boolean enabled);
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
index 3baddd1..a7dc0f4 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
@@ -178,6 +178,16 @@ public class ClientAdapter implements ClientModel {
     }
 
     @Override
+    public String getRegistrationToken() {
+        return entity.getRegistrationToken();
+    }
+
+    @Override
+    public void setRegistrationToken(String registrationToken) {
+        entity.setRegistrationToken(registrationToken);
+    }
+
+    @Override
     public boolean validateSecret(String secret) {
         return secret.equals(entity.getSecret());
     }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
index f26f651..6218e26 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
@@ -42,6 +42,8 @@ public class ClientEntity {
     private boolean enabled;
     @Column(name="SECRET")
     private String secret;
+    @Column(name="REGISTRATION_TOKEN")
+    private String registrationToken;
     @Column(name="CLIENT_AUTHENTICATOR_TYPE")
     private String clientAuthenticatorType;
     @Column(name="NOT_BEFORE")
@@ -201,6 +203,14 @@ public class ClientEntity {
         this.secret = secret;
     }
 
+    public String getRegistrationToken() {
+        return registrationToken;
+    }
+
+    public void setRegistrationToken(String registrationToken) {
+        this.registrationToken = registrationToken;
+    }
+
     public int getNotBefore() {
         return notBefore;
     }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupAttributeEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupAttributeEntity.java
new file mode 100755
index 0000000..87744e1
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupAttributeEntity.java
@@ -0,0 +1,70 @@
+package org.keycloak.models.jpa.entities;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@NamedQueries({
+        @NamedQuery(name="getGroupAttributesByNameAndValue", query="select attr from GroupAttributeEntity attr where attr.name = :name and attr.value = :value"),
+        @NamedQuery(name="deleteGroupAttributesByGroup", query="delete from  GroupAttributeEntity attr where attr.group = :group"),
+        @NamedQuery(name="deleteGroupAttributesByRealm", query="delete from  GroupAttributeEntity attr where attr.group IN (select u from GroupEntity u where u.realm=:realm)")
+})
+@Table(name="GROUP_ATTRIBUTE")
+@Entity
+public class GroupAttributeEntity {
+
+    @Id
+    @Column(name="ID", length = 36)
+    protected String id;
+
+    @ManyToOne(fetch= FetchType.LAZY)
+    @JoinColumn(name = "GROUP_ID")
+    protected GroupEntity group;
+
+    @Column(name = "NAME")
+    protected String name;
+    @Column(name = "VALUE")
+    protected String value;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public GroupEntity getGroup() {
+        return group;
+    }
+
+    public void setGroup(GroupEntity group) {
+        this.group = group;
+    }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupEntity.java
new file mode 100755
index 0000000..2326cad
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupEntity.java
@@ -0,0 +1,108 @@
+package org.keycloak.models.jpa.entities;
+
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@NamedQueries({
+        @NamedQuery(name="getAllGroupsByRealm", query="select u from GroupEntity u where u.realm = :realm order by u.name"),
+        @NamedQuery(name="getGroupById", query="select u from GroupEntity u where u.id = :id and u.realm = :realm"),
+        @NamedQuery(name="getGroupIdsByParent", query="select u.id from GroupEntity u where u.parent = :parent"),
+        @NamedQuery(name="getGroupCount", query="select count(u) from GroupEntity u where u.realm = :realm"),
+        @NamedQuery(name="deleteGroupsByRealm", query="delete from GroupEntity u where u.realm = :realm")
+})
+@Entity
+@Table(name="KEYCLOAK_GROUP")
+public class GroupEntity {
+    @Id
+    @Column(name="ID", length = 36)
+    protected String id;
+
+    @Column(name = "NAME")
+    protected String name;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "PARENT_GROUP")
+    private GroupEntity parent;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "REALM_ID")
+    private RealmEntity realm;
+
+    @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="group")
+    protected Collection<GroupAttributeEntity> attributes = new ArrayList<GroupAttributeEntity>();
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public Collection<GroupAttributeEntity> getAttributes() {
+        return attributes;
+    }
+
+    public void setAttributes(Collection<GroupAttributeEntity> attributes) {
+        this.attributes = attributes;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public RealmEntity getRealm() {
+        return realm;
+    }
+
+    public void setRealm(RealmEntity realm) {
+        this.realm = realm;
+    }
+
+    public GroupEntity getParent() {
+        return parent;
+    }
+
+    public void setParent(GroupEntity parent) {
+        this.parent = parent;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null) return false;
+
+        GroupEntity that = (GroupEntity) o;
+
+        if (!id.equals(that.id)) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return id.hashCode();
+    }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupRoleMappingEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupRoleMappingEntity.java
new file mode 100755
index 0000000..27fe1ac
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupRoleMappingEntity.java
@@ -0,0 +1,100 @@
+package org.keycloak.models.jpa.entities;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@NamedQueries({
+        @NamedQuery(name="groupHasRole", query="select m from GroupRoleMappingEntity m where m.group = :group and m.roleId = :roleId"),
+        @NamedQuery(name="groupRoleMappings", query="select m from GroupRoleMappingEntity m where m.group = :group"),
+        @NamedQuery(name="groupRoleMappingIds", query="select m.roleId from GroupRoleMappingEntity m where m.group = :group"),
+        @NamedQuery(name="deleteGroupRoleMappingsByRealm", query="delete from  GroupRoleMappingEntity mapping where mapping.group IN (select u from GroupEntity u where u.realm=:realm)"),
+        @NamedQuery(name="deleteGroupRoleMappingsByRole", query="delete from GroupRoleMappingEntity m where m.roleId = :roleId"),
+        @NamedQuery(name="deleteGroupRoleMappingsByGroup", query="delete from GroupRoleMappingEntity m where m.group = :group")
+
+})
+@Table(name="GROUP_ROLE_MAPPING")
+@Entity
+@IdClass(GroupRoleMappingEntity.Key.class)
+public class GroupRoleMappingEntity {
+
+    @Id
+    @ManyToOne(fetch= FetchType.LAZY)
+    @JoinColumn(name="GROUP_ID")
+    protected GroupEntity group;
+
+    @Id
+    @Column(name = "ROLE_ID")
+    protected String roleId;
+
+    public GroupEntity getGroup() {
+        return group;
+    }
+
+    public void setGroup(GroupEntity group) {
+        this.group = group;
+    }
+
+    public String getRoleId() {
+        return roleId;
+    }
+
+    public void setRoleId(String roleId) {
+        this.roleId = roleId;
+    }
+
+    public static class Key implements Serializable {
+
+        protected GroupEntity group;
+
+        protected String roleId;
+
+        public Key() {
+        }
+
+        public Key(GroupEntity group, String roleId) {
+            this.group = group;
+            this.roleId = roleId;
+        }
+
+        public GroupEntity getGroup() {
+            return group;
+        }
+
+        public String getRoleId() {
+            return roleId;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            Key key = (Key) o;
+
+            if (!roleId.equals(key.roleId)) return false;
+            if (!group.equals(key.group)) return false;
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = group.hashCode();
+            result = 31 * result + roleId.hashCode();
+            return result;
+        }
+    }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderEntity.java
index eeef707..6bbc31c 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderEntity.java
@@ -11,6 +11,7 @@ import javax.persistence.ManyToOne;
 import javax.persistence.MapKeyColumn;
 import javax.persistence.NamedQueries;
 import javax.persistence.NamedQuery;
+import javax.persistence.OneToOne;
 import javax.persistence.Table;
 import java.util.Map;
 
@@ -41,9 +42,6 @@ public class IdentityProviderEntity {
     @Column(name="ENABLED")
     private boolean enabled;
 
-    @Column(name = "UPDATE_PROFILE_FIRST_LGN_MD")
-    private String updateProfileFirstLoginMode;
-
     @Column(name = "TRUST_EMAIL")
     private boolean trustEmail;
 
@@ -56,6 +54,9 @@ public class IdentityProviderEntity {
     @Column(name="AUTHENTICATE_BY_DEFAULT")
     private boolean authenticateByDefault;
 
+    @Column(name="FIRST_BROKER_LOGIN_FLOW_ID")
+    private String firstBrokerLoginFlowId;
+
     @ElementCollection
     @MapKeyColumn(name="NAME")
     @Column(name="VALUE", columnDefinition = "TEXT")
@@ -102,14 +103,6 @@ public class IdentityProviderEntity {
         this.enabled = enabled;
     }
 
-    public String getUpdateProfileFirstLoginMode() {
-        return updateProfileFirstLoginMode;
-    }
-
-    public void setUpdateProfileFirstLoginMode(String updateProfileFirstLoginMode) {
-        this.updateProfileFirstLoginMode = updateProfileFirstLoginMode;
-    }
-
     public boolean isStoreToken() {
         return this.storeToken;
     }
@@ -126,6 +119,14 @@ public class IdentityProviderEntity {
         this.authenticateByDefault = authenticateByDefault;
     }
 
+    public String getFirstBrokerLoginFlowId() {
+        return firstBrokerLoginFlowId;
+    }
+
+    public void setFirstBrokerLoginFlowId(String firstBrokerLoginFlowId) {
+        this.firstBrokerLoginFlowId = firstBrokerLoginFlowId;
+    }
+
     public Map<String, String> getConfig() {
         return this.config;
     }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index bf5b339..6492b81 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -120,7 +120,7 @@ public class RealmEntity {
     Collection<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>();
 
     @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true)
-    @JoinTable(name="FED_PROVIDERS", joinColumns={ @JoinColumn(name="REALM_ID") })
+    @JoinTable(name="FED_PROVIDERS", joinColumns={ @JoinColumn(name="REALM_ID") }, inverseJoinColumns={ @JoinColumn(name = "USERFEDERATIONPROVIDERS_ID") })
     List<UserFederationProviderEntity> userFederationProviders = new ArrayList<UserFederationProviderEntity>();
 
     @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
@@ -143,6 +143,10 @@ public class RealmEntity {
     @JoinTable(name="REALM_DEFAULT_ROLES", joinColumns = { @JoinColumn(name="REALM_ID")}, inverseJoinColumns = { @JoinColumn(name="ROLE_ID")})
     protected Collection<RoleEntity> defaultRoles = new ArrayList<RoleEntity>();
 
+    @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
+    @JoinTable(name="REALM_DEFAULT_GROUPS", joinColumns = { @JoinColumn(name="REALM_ID")}, inverseJoinColumns = { @JoinColumn(name="GROUP_ID")})
+    protected Collection<GroupEntity> defaultGroups = new ArrayList<>();
+
     @Column(name="EVENTS_ENABLED")
     protected boolean eventsEnabled;
     @Column(name="EVENTS_EXPIRATION")
@@ -426,6 +430,14 @@ public class RealmEntity {
         this.defaultRoles = defaultRoles;
     }
 
+    public Collection<GroupEntity> getDefaultGroups() {
+        return defaultGroups;
+    }
+
+    public void setDefaultGroups(Collection<GroupEntity> defaultGroups) {
+        this.defaultGroups = defaultGroups;
+    }
+
     public String getPasswordPolicy() {
         return passwordPolicy;
     }
@@ -718,5 +730,6 @@ public class RealmEntity {
     public void setClientAuthenticationFlow(String clientAuthenticationFlow) {
         this.clientAuthenticationFlow = clientAuthenticationFlow;
     }
+
 }
 
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java
index 437867e..ddf9244 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java
@@ -30,7 +30,7 @@ import java.util.Collection;
 
 public class RoleEntity {
     @Id
-    @Column(name="id", length = 36)
+    @Column(name="ID", length = 36)
     private String id;
 
     @Column(name = "NAME")
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
index 2da1641..5051ade 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
@@ -77,7 +77,7 @@ public class UserEntity {
     @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="user")
     protected Collection<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
 
-    @Column(name="federation_link")
+    @Column(name="FEDERATION_LINK")
     protected String federationLink;
 
     @Column(name="SERVICE_ACCOUNT_CLIENT_LINK")
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationProviderEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationProviderEntity.java
index 22370f3..259c974 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationProviderEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationProviderEntity.java
@@ -35,7 +35,7 @@ public class UserFederationProviderEntity {
 
     @ElementCollection
     @MapKeyColumn(name="name")
-    @Column(name="value")
+    @Column(name="VALUE")
     @CollectionTable(name="USER_FEDERATION_CONFIG", joinColumns={ @JoinColumn(name="USER_FEDERATION_PROVIDER_ID") })
     private Map<String, String> config;
 
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserGroupMembershipEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserGroupMembershipEntity.java
new file mode 100755
index 0000000..76b7797
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserGroupMembershipEntity.java
@@ -0,0 +1,102 @@
+package org.keycloak.models.jpa.entities;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@NamedQueries({
+        @NamedQuery(name="userMemberOf", query="select m from UserGroupMembershipEntity m where m.user = :user and m.groupId = :groupId"),
+        @NamedQuery(name="userGroupMembership", query="select m from UserGroupMembershipEntity m where m.user = :user"),
+        @NamedQuery(name="groupMembership", query="select g.user from UserGroupMembershipEntity g where g.groupId = :groupId"),
+        @NamedQuery(name="userGroupIds", query="select m.groupId from UserGroupMembershipEntity m where m.user = :user"),
+        @NamedQuery(name="deleteUserGroupMembershipByRealm", query="delete from  UserGroupMembershipEntity mapping where mapping.user IN (select u from UserEntity u where u.realmId=:realmId)"),
+        @NamedQuery(name="deleteUserGroupMembershipsByRealmAndLink", query="delete from  UserGroupMembershipEntity mapping where mapping.user IN (select u from UserEntity u where u.realmId=:realmId and u.federationLink=:link)"),
+        @NamedQuery(name="deleteUserGroupMembershipsByGroup", query="delete from UserGroupMembershipEntity m where m.groupId = :groupId"),
+        @NamedQuery(name="deleteUserGroupMembershipsByUser", query="delete from UserGroupMembershipEntity m where m.user = :user")
+
+})
+@Table(name="USER_GROUP_MEMBERSHIP")
+@Entity
+@IdClass(UserGroupMembershipEntity.Key.class)
+public class UserGroupMembershipEntity {
+
+    @Id
+    @ManyToOne(fetch= FetchType.LAZY)
+    @JoinColumn(name="USER_ID")
+    protected UserEntity user;
+
+    @Id
+    @Column(name = "GROUP_ID")
+    protected String groupId;
+
+    public UserEntity getUser() {
+        return user;
+    }
+
+    public void setUser(UserEntity user) {
+        this.user = user;
+    }
+
+    public String getGroupId() {
+        return groupId;
+    }
+
+    public void setGroupId(String groupId) {
+        this.groupId = groupId;
+    }
+
+    public static class Key implements Serializable {
+
+        protected UserEntity user;
+
+        protected String groupId;
+
+        public Key() {
+        }
+
+        public Key(UserEntity user, String groupId) {
+            this.user = user;
+            this.groupId = groupId;
+        }
+
+        public UserEntity getUser() {
+            return user;
+        }
+
+        public String getGroupId() {
+            return groupId;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            Key key = (Key) o;
+
+            if (!groupId.equals(key.groupId)) return false;
+            if (!user.equals(key.user)) return false;
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = user.hashCode();
+            result = 31 * result + groupId.hashCode();
+            return result;
+        }
+    }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/GroupAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/GroupAdapter.java
new file mode 100755
index 0000000..0fb9c2f
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/GroupAdapter.java
@@ -0,0 +1,328 @@
+package org.keycloak.models.jpa;
+
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.common.util.Time;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.OTPPolicy;
+import org.keycloak.models.PasswordPolicy;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleContainerModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserConsentModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.jpa.entities.CredentialEntity;
+import org.keycloak.models.jpa.entities.GroupAttributeEntity;
+import org.keycloak.models.jpa.entities.GroupEntity;
+import org.keycloak.models.jpa.entities.GroupRoleMappingEntity;
+import org.keycloak.models.jpa.entities.RoleEntity;
+import org.keycloak.models.jpa.entities.UserAttributeEntity;
+import org.keycloak.models.jpa.entities.UserConsentEntity;
+import org.keycloak.models.jpa.entities.UserConsentProtocolMapperEntity;
+import org.keycloak.models.jpa.entities.UserConsentRoleEntity;
+import org.keycloak.models.jpa.entities.UserEntity;
+import org.keycloak.models.jpa.entities.UserRequiredActionEntity;
+import org.keycloak.models.jpa.entities.UserRoleMappingEntity;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
+
+import javax.persistence.EntityManager;
+import javax.persistence.TypedQuery;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class GroupAdapter implements GroupModel {
+
+    protected GroupEntity group;
+    protected EntityManager em;
+    protected RealmModel realm;
+
+    public GroupAdapter(RealmModel realm, EntityManager em, GroupEntity group) {
+        this.em = em;
+        this.group = group;
+        this.realm = realm;
+    }
+
+    public GroupEntity getGroup() {
+        return group;
+    }
+
+    @Override
+    public String getId() {
+        return group.getId();
+    }
+
+    @Override
+    public String getName() {
+        return group.getName();
+    }
+
+    @Override
+    public void setName(String name) {
+        group.setName(name);
+    }
+
+    @Override
+    public GroupModel getParent() {
+        GroupEntity parent = group.getParent();
+        if (parent == null) return null;
+        return realm.getGroupById(parent.getId());
+    }
+
+    @Override
+    public String getParentId() {
+        GroupEntity parent = group.getParent();
+        if (parent == null) return null;
+        return parent.getId();
+    }
+
+    public static GroupEntity toEntity(GroupModel model, EntityManager em) {
+        if (model instanceof GroupAdapter) {
+            return ((GroupAdapter)model).getGroup();
+        }
+        return em.getReference(GroupEntity.class, model.getId());
+    }
+
+    @Override
+    public void setParent(GroupModel parent) {
+        if (parent == null) group.setParent(null);
+        else {
+            GroupEntity parentEntity = toEntity(parent, em);
+            group.setParent(parentEntity);
+        }
+    }
+
+    @Override
+    public void addChild(GroupModel subGroup) {
+        subGroup.setParent(this);
+    }
+
+    @Override
+    public void removeChild(GroupModel subGroup) {
+        subGroup.setParent(null);
+    }
+
+    @Override
+    public Set<GroupModel> getSubGroups() {
+        TypedQuery<String> query = em.createNamedQuery("getGroupIdsByParent", String.class);
+        query.setParameter("parent", group);
+        List<String> ids = query.getResultList();
+        Set<GroupModel> set = new HashSet<>();
+        for (String id : ids) {
+            GroupModel subGroup = realm.getGroupById(id);
+            if (subGroup == null) continue;
+            set.add(subGroup);
+        }
+        return set;
+    }
+
+    @Override
+    public void setSingleAttribute(String name, String value) {
+        boolean found = false;
+        List<GroupAttributeEntity> toRemove = new ArrayList<>();
+        for (GroupAttributeEntity attr : group.getAttributes()) {
+            if (attr.getName().equals(name)) {
+                if (!found) {
+                    attr.setValue(value);
+                    found = true;
+                } else {
+                    toRemove.add(attr);
+                }
+            }
+        }
+
+        for (GroupAttributeEntity attr : toRemove) {
+            em.remove(attr);
+            group.getAttributes().remove(attr);
+        }
+
+        if (found) {
+            return;
+        }
+
+        persistAttributeValue(name, value);
+    }
+
+    @Override
+    public void setAttribute(String name, List<String> values) {
+        // Remove all existing
+        removeAttribute(name);
+
+        // Put all new
+        for (String value : values) {
+            persistAttributeValue(name, value);
+        }
+    }
+
+    private void persistAttributeValue(String name, String value) {
+        GroupAttributeEntity attr = new GroupAttributeEntity();
+        attr.setId(KeycloakModelUtils.generateId());
+        attr.setName(name);
+        attr.setValue(value);
+        attr.setGroup(group);
+        em.persist(attr);
+        group.getAttributes().add(attr);
+    }
+
+    @Override
+    public void removeAttribute(String name) {
+        Iterator<GroupAttributeEntity> it = group.getAttributes().iterator();
+        while (it.hasNext()) {
+            GroupAttributeEntity attr = it.next();
+            if (attr.getName().equals(name)) {
+                it.remove();
+                em.remove(attr);
+            }
+        }
+    }
+
+    @Override
+    public String getFirstAttribute(String name) {
+        for (GroupAttributeEntity attr : group.getAttributes()) {
+            if (attr.getName().equals(name)) {
+                return attr.getValue();
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public List<String> getAttribute(String name) {
+        List<String> result = new ArrayList<>();
+        for (GroupAttributeEntity attr : group.getAttributes()) {
+            if (attr.getName().equals(name)) {
+                result.add(attr.getValue());
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public Map<String, List<String>> getAttributes() {
+        MultivaluedHashMap<String, String> result = new MultivaluedHashMap<>();
+        for (GroupAttributeEntity attr : group.getAttributes()) {
+            result.add(attr.getName(), attr.getValue());
+        }
+        return result;
+    }
+
+    @Override
+    public boolean hasRole(RoleModel role) {
+        Set<RoleModel> roles = getRoleMappings();
+        return KeycloakModelUtils.hasRole(roles, role);
+    }
+
+    protected TypedQuery<GroupRoleMappingEntity> getGroupRoleMappingEntityTypedQuery(RoleModel role) {
+        TypedQuery<GroupRoleMappingEntity> query = em.createNamedQuery("groupHasRole", GroupRoleMappingEntity.class);
+        query.setParameter("group", getGroup());
+        query.setParameter("roleId", role.getId());
+        return query;
+    }
+
+    @Override
+    public void grantRole(RoleModel role) {
+        if (hasRole(role)) return;
+        GroupRoleMappingEntity entity = new GroupRoleMappingEntity();
+        entity.setGroup(getGroup());
+        entity.setRoleId(role.getId());
+        em.persist(entity);
+        em.flush();
+        em.detach(entity);
+    }
+
+    @Override
+    public Set<RoleModel> getRealmRoleMappings() {
+        Set<RoleModel> roleMappings = getRoleMappings();
+
+        Set<RoleModel> realmRoles = new HashSet<RoleModel>();
+        for (RoleModel role : roleMappings) {
+            RoleContainerModel container = role.getContainer();
+            if (container instanceof RealmModel) {
+                realmRoles.add(role);
+            }
+        }
+        return realmRoles;
+    }
+
+
+    @Override
+    public Set<RoleModel> getRoleMappings() {
+        // we query ids only as the role might be cached and following the @ManyToOne will result in a load
+        // even if we're getting just the id.
+        TypedQuery<String> query = em.createNamedQuery("groupRoleMappingIds", String.class);
+        query.setParameter("group", getGroup());
+        List<String> ids = query.getResultList();
+        Set<RoleModel> roles = new HashSet<RoleModel>();
+        for (String roleId : ids) {
+            RoleModel roleById = realm.getRoleById(roleId);
+            if (roleById == null) continue;
+            roles.add(roleById);
+        }
+        return roles;
+    }
+
+    @Override
+    public void deleteRoleMapping(RoleModel role) {
+        if (group == null || role == null) return;
+
+        TypedQuery<GroupRoleMappingEntity> query = getGroupRoleMappingEntityTypedQuery(role);
+        List<GroupRoleMappingEntity> results = query.getResultList();
+        if (results.size() == 0) return;
+        for (GroupRoleMappingEntity entity : results) {
+            em.remove(entity);
+        }
+        em.flush();
+    }
+
+    @Override
+    public Set<RoleModel> getClientRoleMappings(ClientModel app) {
+        Set<RoleModel> roleMappings = getRoleMappings();
+
+        Set<RoleModel> roles = new HashSet<RoleModel>();
+        for (RoleModel role : roleMappings) {
+            RoleContainerModel container = role.getContainer();
+            if (container instanceof ClientModel) {
+                ClientModel appModel = (ClientModel)container;
+                if (appModel.getId().equals(app.getId())) {
+                   roles.add(role);
+                }
+            }
+        }
+        return roles;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || !(o instanceof GroupModel)) return false;
+
+        GroupModel that = (GroupModel) o;
+        return that.getId().equals(getId());
+    }
+
+    @Override
+    public int hashCode() {
+        return getId().hashCode();
+    }
+
+
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
index b9229bb..09b4fa2 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
@@ -2,11 +2,13 @@ package org.keycloak.models.jpa;
 
 import org.keycloak.migration.MigrationModel;
 import org.keycloak.models.ClientModel;
+import org.keycloak.models.GroupModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RealmProvider;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.jpa.entities.ClientEntity;
+import org.keycloak.models.jpa.entities.GroupEntity;
 import org.keycloak.models.jpa.entities.RealmEntity;
 import org.keycloak.models.jpa.entities.RoleEntity;
 import org.keycloak.models.utils.KeycloakModelUtils;
@@ -97,11 +99,20 @@ public class JpaRealmProvider implements RealmProvider {
 
         RealmAdapter adapter = new RealmAdapter(session, em, realm);
         session.users().preRemove(adapter);
+        int num = em.createNamedQuery("deleteGroupRoleMappingsByRealm")
+                .setParameter("realm", realm).executeUpdate();
+        num = em.createNamedQuery("deleteGroupAttributesByRealm")
+                .setParameter("realm", realm).executeUpdate();
+        num = em.createNamedQuery("deleteGroupsByRealm")
+                .setParameter("realm", realm).executeUpdate();
         for (ClientEntity a : new LinkedList<>(realm.getClients())) {
             adapter.removeClient(a.getId());
         }
 
         em.remove(realm);
+
+        em.flush();
+        em.clear();
         return true;
     }
 
@@ -118,6 +129,14 @@ public class JpaRealmProvider implements RealmProvider {
     }
 
     @Override
+    public GroupModel getGroupById(String id, RealmModel realm) {
+        GroupEntity groupEntity = em.find(GroupEntity.class, id);
+        if (groupEntity == null) return null;
+        if (!groupEntity.getRealm().getId().equals(realm.getId())) return null;
+        return new GroupAdapter(realm, em, groupEntity);
+    }
+
+    @Override
     public ClientModel getClientById(String id, RealmModel realm) {
         ClientEntity app = em.find(ClientEntity.class, id);
 
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
index c903147..64650c9 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
@@ -3,6 +3,7 @@ package org.keycloak.models.jpa;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.CredentialValidationOutput;
 import org.keycloak.models.FederatedIdentityModel;
+import org.keycloak.models.GroupModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RealmModel;
@@ -70,6 +71,10 @@ public class JpaUserProvider implements UserProvider {
                     userModel.grantRole(application.getRole(r));
                 }
             }
+
+            for (GroupModel g : realm.getDefaultGroups()) {
+                userModel.joinGroup(g);
+            }
         }
         for (RequiredActionProviderModel r : realm.getRequiredActionProviders()) {
             if (r.isEnabled() && r.isDefaultAction()) {
@@ -96,6 +101,7 @@ public class JpaUserProvider implements UserProvider {
     private void removeUser(UserEntity user) {
         String id = user.getId();
         em.createNamedQuery("deleteUserRoleMappingsByUser").setParameter("user", user).executeUpdate();
+        em.createNamedQuery("deleteUserGroupMembershipsByUser").setParameter("user", user).executeUpdate();
         em.createNamedQuery("deleteFederatedIdentityByUser").setParameter("user", user).executeUpdate();
         em.createNamedQuery("deleteUserConsentRolesByUser").setParameter("user", user).executeUpdate();
         em.createNamedQuery("deleteUserConsentProtMappersByUser").setParameter("user", user).executeUpdate();
@@ -173,6 +179,8 @@ public class JpaUserProvider implements UserProvider {
                 .setParameter("realmId", realm.getId()).executeUpdate();
         num = em.createNamedQuery("deleteUserAttributesByRealm")
                 .setParameter("realmId", realm.getId()).executeUpdate();
+        num = em.createNamedQuery("deleteUserGroupMembershipByRealm")
+                .setParameter("realmId", realm.getId()).executeUpdate();
         num = em.createNamedQuery("deleteUsersByRealm")
                 .setParameter("realmId", realm.getId()).executeUpdate();
     }
@@ -226,6 +234,25 @@ public class JpaUserProvider implements UserProvider {
     }
 
     @Override
+    public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group) {
+        TypedQuery<UserEntity> query = em.createNamedQuery("groupMembership", UserEntity.class);
+        query.setParameter("groupId", group.getId());
+        List<UserEntity> results = query.getResultList();
+
+        List<UserModel> users = new ArrayList<UserModel>();
+        for (UserEntity user : results) {
+            users.add(new UserAdapter(realm, em, user));
+        }
+        return users;
+    }
+
+    @Override
+    public void preRemove(RealmModel realm, GroupModel group) {
+        em.createNamedQuery("deleteUserGroupMembershipsByGroup").setParameter("groupId", group.getId()).executeUpdate();
+
+    }
+
+    @Override
     public UserModel getUserById(String id, RealmModel realm) {
         TypedQuery<UserEntity> query = em.createNamedQuery("getRealmUserById", UserEntity.class);
         query.setParameter("id", id);
@@ -325,6 +352,25 @@ public class JpaUserProvider implements UserProvider {
     }
 
     @Override
+    public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
+        TypedQuery<UserEntity> query = em.createNamedQuery("groupMembership", UserEntity.class);
+        query.setParameter("groupId", group.getId());
+        if (firstResult != -1) {
+            query.setFirstResult(firstResult);
+        }
+        if (maxResults != -1) {
+            query.setMaxResults(maxResults);
+        }
+        List<UserEntity> results = query.getResultList();
+
+        List<UserModel> users = new ArrayList<UserModel>();
+        for (UserEntity user : results) {
+            users.add(new UserAdapter(realm, em, user));
+        }
+        return users;
+    }
+
+    @Override
     public List<UserModel> searchForUser(String search, RealmModel realm) {
         return searchForUser(search, realm, -1, -1);
     }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 86f4490..1397cad 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -1,11 +1,13 @@
 package org.keycloak.models.jpa;
 
+import org.keycloak.Config;
 import org.keycloak.connections.jpa.util.JpaUtils;
 import org.keycloak.common.enums.SslRequired;
 import org.keycloak.models.AuthenticationExecutionModel;
 import org.keycloak.models.AuthenticationFlowModel;
 import org.keycloak.models.AuthenticatorConfigModel;
 import org.keycloak.models.ClientModel;
+import org.keycloak.models.GroupModel;
 import org.keycloak.models.IdentityProviderMapperModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.KeycloakSession;
@@ -24,6 +26,7 @@ import org.keycloak.models.jpa.entities.AuthenticationExecutionEntity;
 import org.keycloak.models.jpa.entities.AuthenticationFlowEntity;
 import org.keycloak.models.jpa.entities.AuthenticatorConfigEntity;
 import org.keycloak.models.jpa.entities.ClientEntity;
+import org.keycloak.models.jpa.entities.GroupEntity;
 import org.keycloak.models.jpa.entities.IdentityProviderEntity;
 import org.keycloak.models.jpa.entities.IdentityProviderMapperEntity;
 import org.keycloak.models.jpa.entities.RealmAttributeEntity;
@@ -645,6 +648,44 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
+    public List<GroupModel> getDefaultGroups() {
+        Collection<GroupEntity> entities = realm.getDefaultGroups();
+        List<GroupModel> defaultGroups = new LinkedList<>();
+        for (GroupEntity entity : entities) {
+            defaultGroups.add(session.realms().getGroupById(entity.getId(), this));
+        }
+        return defaultGroups;
+    }
+
+    @Override
+    public void addDefaultGroup(GroupModel group) {
+        Collection<GroupEntity> entities = realm.getDefaultGroups();
+        for (GroupEntity entity : entities) {
+            if (entity.getId().equals(group.getId())) return;
+        }
+        GroupEntity groupEntity = GroupAdapter.toEntity(group, em);
+        realm.getDefaultGroups().add(groupEntity);
+        em.flush();
+
+    }
+
+    @Override
+    public void removeDefaultGroup(GroupModel group) {
+        GroupEntity found = null;
+        for (GroupEntity defaultGroup : realm.getDefaultGroups()) {
+            if (defaultGroup.getId().equals(group.getId())) {
+                found = defaultGroup;
+                break;
+            }
+        }
+        if (found != null) {
+            realm.getDefaultGroups().remove(found);
+            em.flush();
+        }
+
+    }
+
+    @Override
     public Map<String, ClientModel> getClientNameMap() {
         Map<String, ClientModel> map = new HashMap<String, ClientModel>();
         for (ClientModel app : getClients()) {
@@ -997,6 +1038,7 @@ public class RealmAdapter implements RealmModel {
         String compositeRoleTable = JpaUtils.getTableNameForNativeQuery("COMPOSITE_ROLE", em);
         em.createNativeQuery("delete from " + compositeRoleTable + " where CHILD_ROLE = :role").setParameter("role", roleEntity).executeUpdate();
         em.createNamedQuery("deleteScopeMappingByRole").setParameter("role", roleEntity).executeUpdate();
+        em.createNamedQuery("deleteGroupRoleMappingsByRole").setParameter("roleId", roleEntity.getId()).executeUpdate();
 
         em.remove(roleEntity);
 
@@ -1193,8 +1235,13 @@ public class RealmAdapter implements RealmModel {
     
     @Override
     public ClientModel getMasterAdminClient() {
-        ClientEntity client = realm.getMasterAdminClient();
-        return client!=null ? new ClientAdapter(this, em, session, realm.getMasterAdminClient()) : null;
+        ClientEntity masterAdminClient = realm.getMasterAdminClient();
+        if (masterAdminClient == null) {
+            return null;
+        }
+
+        RealmAdapter masterRealm = new RealmAdapter(session, em, masterAdminClient.getRealm());
+        return new ClientAdapter(masterRealm, em, session, masterAdminClient);
     }
 
     @Override
@@ -1216,9 +1263,9 @@ public class RealmAdapter implements RealmModel {
             identityProviderModel.setInternalId(entity.getInternalId());
             identityProviderModel.setConfig(entity.getConfig());
             identityProviderModel.setEnabled(entity.isEnabled());
-            identityProviderModel.setUpdateProfileFirstLoginMode(entity.getUpdateProfileFirstLoginMode());
             identityProviderModel.setTrustEmail(entity.isTrustEmail());
             identityProviderModel.setAuthenticateByDefault(entity.isAuthenticateByDefault());
+            identityProviderModel.setFirstBrokerLoginFlowId(entity.getFirstBrokerLoginFlowId());
             identityProviderModel.setStoreToken(entity.isStoreToken());
             identityProviderModel.setAddReadTokenRoleOnCreate(entity.isAddReadTokenRoleOnCreate());
 
@@ -1249,9 +1296,9 @@ public class RealmAdapter implements RealmModel {
         entity.setEnabled(identityProvider.isEnabled());
         entity.setStoreToken(identityProvider.isStoreToken());
         entity.setAddReadTokenRoleOnCreate(identityProvider.isAddReadTokenRoleOnCreate());
-        entity.setUpdateProfileFirstLoginMode(identityProvider.getUpdateProfileFirstLoginMode());
         entity.setTrustEmail(identityProvider.isTrustEmail());
         entity.setAuthenticateByDefault(identityProvider.isAuthenticateByDefault());
+        entity.setFirstBrokerLoginFlowId(identityProvider.getFirstBrokerLoginFlowId());
         entity.setConfig(identityProvider.getConfig());
 
         realm.addIdentityProvider(entity);
@@ -1276,9 +1323,9 @@ public class RealmAdapter implements RealmModel {
             if (entity.getInternalId().equals(identityProvider.getInternalId())) {
                 entity.setAlias(identityProvider.getAlias());
                 entity.setEnabled(identityProvider.isEnabled());
-                entity.setUpdateProfileFirstLoginMode(identityProvider.getUpdateProfileFirstLoginMode());
                 entity.setTrustEmail(identityProvider.isTrustEmail());
                 entity.setAuthenticateByDefault(identityProvider.isAuthenticateByDefault());
+                entity.setFirstBrokerLoginFlowId(identityProvider.getFirstBrokerLoginFlowId());
                 entity.setAddReadTokenRoleOnCreate(identityProvider.isAddReadTokenRoleOnCreate());
                 entity.setStoreToken(identityProvider.isStoreToken());
                 entity.setConfig(identityProvider.getConfig());
@@ -1944,4 +1991,91 @@ public class RealmAdapter implements RealmModel {
         }
         return null;
     }
+
+    @Override
+    public void moveGroup(GroupModel group, GroupModel toParent) {
+        if (group.getParentId() != null) {
+            group.getParent().removeChild(group);
+        }
+        group.setParent(toParent);
+        if (toParent != null) toParent.addChild(group);
+        else addTopLevelGroup(group);
+    }
+
+    @Override
+    public GroupModel getGroupById(String id) {
+        return session.realms().getGroupById(id, this);
+    }
+
+    @Override
+    public List<GroupModel> getGroups() {
+        List<GroupModel> list = new LinkedList<>();
+        Collection<GroupEntity> groups =  em.createNamedQuery("getAllGroupsByRealm").setParameter("realm", realm).getResultList();
+        if (groups == null) return list;
+        for (GroupEntity entity : groups) {
+            list.add(new GroupAdapter(this, em, entity));
+        }
+        return list;
+    }
+
+    @Override
+    public List<GroupModel> getTopLevelGroups() {
+        List<GroupModel> all = getGroups();
+        Iterator<GroupModel> it = all.iterator();
+        while (it.hasNext()) {
+            GroupModel group = it.next();
+            if (group.getParent() != null) {
+                it.remove();
+            }
+        }
+        return all;
+    }
+
+    @Override
+    public boolean removeGroup(GroupModel group) {
+        if (group == null) {
+            return false;
+        }
+        GroupEntity groupEntity = GroupAdapter.toEntity(group, em);
+        if (!groupEntity.getRealm().getId().equals(getId())) {
+            return false;
+        }
+        realm.getDefaultRoles().remove(groupEntity);
+        for (GroupModel subGroup : group.getSubGroups()) {
+            removeGroup(subGroup);
+        }
+
+
+        session.users().preRemove(this, group);
+        moveGroup(group, null);
+        em.createNamedQuery("deleteGroupAttributesByGroup").setParameter("group", groupEntity).executeUpdate();
+        em.createNamedQuery("deleteGroupRoleMappingsByGroup").setParameter("group", groupEntity).executeUpdate();
+        em.remove(groupEntity);
+        return true;
+
+
+    }
+
+    @Override
+    public GroupModel createGroup(String name) {
+        String id = KeycloakModelUtils.generateId();
+        return createGroup(id, name);
+    }
+
+    @Override
+    public GroupModel createGroup(String id, String name) {
+        if (id == null) id = KeycloakModelUtils.generateId();
+        GroupEntity groupEntity = new GroupEntity();
+        groupEntity.setId(id);
+        groupEntity.setName(name);
+        groupEntity.setRealm(realm);
+        em.persist(groupEntity);
+
+        return new GroupAdapter(this, em, groupEntity);
+    }
+
+    @Override
+    public void addTopLevelGroup(GroupModel subGroup) {
+        subGroup.setParent(null);
+    }
 }
\ No newline at end of file
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
index b370886..5246890 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
@@ -1,6 +1,7 @@
 package org.keycloak.models.jpa;
 
 import org.keycloak.models.ClientModel;
+import org.keycloak.models.GroupModel;
 import org.keycloak.models.OTPPolicy;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.UserConsentModel;
@@ -19,6 +20,7 @@ import org.keycloak.models.jpa.entities.UserConsentProtocolMapperEntity;
 import org.keycloak.models.jpa.entities.UserConsentRoleEntity;
 import org.keycloak.models.jpa.entities.UserAttributeEntity;
 import org.keycloak.models.jpa.entities.UserEntity;
+import org.keycloak.models.jpa.entities.UserGroupMembershipEntity;
 import org.keycloak.models.jpa.entities.UserRequiredActionEntity;
 import org.keycloak.models.jpa.entities.UserRoleMappingEntity;
 import org.keycloak.models.utils.KeycloakModelUtils;
@@ -485,6 +487,63 @@ public class UserAdapter implements UserModel {
         em.flush();
     }
 
+
+    @Override
+    public Set<GroupModel> getGroups() {
+        // we query ids only as the group  might be cached and following the @ManyToOne will result in a load
+        // even if we're getting just the id.
+        TypedQuery<String> query = em.createNamedQuery("userGroupIds", String.class);
+        query.setParameter("user", getUser());
+        List<String> ids = query.getResultList();
+        Set<GroupModel> groups = new HashSet<>();
+        for (String groupId : ids) {
+            GroupModel group = realm.getGroupById(groupId);
+            if (group == null) continue;
+            groups.add(group);
+        }
+        return groups;
+    }
+
+    @Override
+    public void joinGroup(GroupModel group) {
+        if (isMemberOf(group)) return;
+        UserGroupMembershipEntity entity = new UserGroupMembershipEntity();
+        entity.setUser(getUser());
+        entity.setGroupId(group.getId());
+        em.persist(entity);
+        em.flush();
+        em.detach(entity);
+
+    }
+
+    @Override
+    public void leaveGroup(GroupModel group) {
+        if (user == null || group == null) return;
+
+        TypedQuery<UserGroupMembershipEntity> query = getUserGroupMappingQuery(group);
+        List<UserGroupMembershipEntity> results = query.getResultList();
+        if (results.size() == 0) return;
+        for (UserGroupMembershipEntity entity : results) {
+            em.remove(entity);
+        }
+        em.flush();
+
+    }
+
+    @Override
+    public boolean isMemberOf(GroupModel group) {
+        Set<GroupModel> roles = getGroups();
+        return KeycloakModelUtils.isMember(roles, group);
+    }
+
+    protected TypedQuery<UserGroupMembershipEntity> getUserGroupMappingQuery(GroupModel group) {
+        TypedQuery<UserGroupMembershipEntity> query = em.createNamedQuery("userMemberOf", UserGroupMembershipEntity.class);
+        query.setParameter("user", getUser());
+        query.setParameter("groupId", group.getId());
+        return query;
+    }
+
+
     @Override
     public boolean hasRole(RoleModel role) {
         Set<RoleModel> roles = getRoleMappings();
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
index e99f142..8eb562f 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
@@ -178,6 +178,17 @@ public class ClientAdapter extends AbstractMongoAdapter<MongoClientEntity> imple
     }
 
     @Override
+    public String getRegistrationToken() {
+        return getMongoEntity().getRegistrationToken();
+    }
+
+    @Override
+    public void setRegistrationToken(String registrationToken) {
+        getMongoEntity().setRegistrationToken(registrationToken);
+        updateMongoEntity();
+    }
+
+    @Override
     public boolean isPublicClient() {
         return getMongoEntity().isPublicClient();
     }
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/GroupAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/GroupAdapter.java
new file mode 100755
index 0000000..180eb9d
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/GroupAdapter.java
@@ -0,0 +1,246 @@
+package org.keycloak.models.mongo.keycloak.adapters;
+
+import com.mongodb.DBObject;
+import com.mongodb.QueryBuilder;
+import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleContainerModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.mongo.keycloak.entities.MongoClientEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoGroupEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
+import org.keycloak.models.mongo.utils.MongoModelUtils;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class GroupAdapter extends AbstractMongoAdapter<MongoGroupEntity> implements GroupModel {
+
+    private final MongoGroupEntity group;
+    private RealmModel realm;
+    private KeycloakSession session;
+
+    public GroupAdapter(KeycloakSession session, RealmModel realm, MongoGroupEntity group, MongoStoreInvocationContext invContext) {
+        super(invContext);
+        this.group = group;
+        this.realm = realm;
+        this.session = session;
+    }
+
+    @Override
+    public String getId() {
+        return group.getId();
+    }
+
+    @Override
+    public String getName() {
+        return group.getName();
+    }
+
+    @Override
+    public void setName(String name) {
+        group.setName(name);
+        updateGroup();
+    }
+
+    protected void updateGroup() {
+        super.updateMongoEntity();
+    }
+
+    @Override
+    public MongoGroupEntity getMongoEntity() {
+        return group;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || !(o instanceof GroupModel)) return false;
+
+        GroupModel that = (GroupModel) o;
+        return that.getId().equals(getId());
+    }
+
+    @Override
+    public int hashCode() {
+        return getId().hashCode();
+    }
+
+    @Override
+    public void setSingleAttribute(String name, String value) {
+        if (group.getAttributes() == null) {
+            group.setAttributes(new HashMap<String, List<String>>());
+        }
+
+        List<String> attrValues = new ArrayList<>();
+        attrValues.add(value);
+        group.getAttributes().put(name, attrValues);
+        updateGroup();
+    }
+
+    @Override
+    public void setAttribute(String name, List<String> values) {
+        if (group.getAttributes() == null) {
+            group.setAttributes(new HashMap<String, List<String>>());
+        }
+
+        group.getAttributes().put(name, values);
+        updateGroup();
+    }
+
+    @Override
+    public void removeAttribute(String name) {
+        if (group.getAttributes() == null) return;
+
+        group.getAttributes().remove(name);
+        updateGroup();
+    }
+
+    @Override
+    public String getFirstAttribute(String name) {
+        if (group.getAttributes()==null) return null;
+
+        List<String> attrValues = group.getAttributes().get(name);
+        return (attrValues==null || attrValues.isEmpty()) ? null : attrValues.get(0);
+    }
+
+    @Override
+    public List<String> getAttribute(String name) {
+        if (group.getAttributes()==null) return Collections.<String>emptyList();
+        List<String> attrValues = group.getAttributes().get(name);
+        return (attrValues == null) ? Collections.<String>emptyList() : Collections.unmodifiableList(attrValues);
+    }
+
+    @Override
+    public Map<String, List<String>> getAttributes() {
+        return group.getAttributes()==null ? Collections.<String, List<String>>emptyMap() : Collections.unmodifiableMap((Map) group.getAttributes());
+    }
+
+    @Override
+    public boolean hasRole(RoleModel role) {
+        Set<RoleModel> roles = getRoleMappings();
+        return KeycloakModelUtils.hasRole(roles, role);
+    }
+
+    @Override
+    public void grantRole(RoleModel role) {
+        getMongoStore().pushItemToList(group, "roleIds", role.getId(), true, invocationContext);
+    }
+
+    @Override
+    public Set<RoleModel> getRoleMappings() {
+        if (group.getRoleIds() == null || group.getRoleIds().isEmpty()) return Collections.EMPTY_SET;
+        Set<RoleModel> roles = new HashSet<>();
+        for (String id : group.getRoleIds()) {
+            RoleModel roleById = realm.getRoleById(id);
+            if (roleById == null) {
+                throw new ModelException("role does not exist in group role mappings");
+            }
+            roles.add(roleById);
+        }
+        return roles;
+     }
+
+    @Override
+    public Set<RoleModel> getRealmRoleMappings() {
+        Set<RoleModel> allRoles = getRoleMappings();
+
+        // Filter to retrieve just realm roles
+        Set<RoleModel> realmRoles = new HashSet<RoleModel>();
+        for (RoleModel role : allRoles) {
+            if (role.getContainer() instanceof RealmModel) {
+                realmRoles.add(role);
+            }
+        }
+        return realmRoles;
+    }
+
+    @Override
+    public void deleteRoleMapping(RoleModel role) {
+        if (group == null || role == null) return;
+
+        getMongoStore().pullItemFromList(group, "roleIds", role.getId(), invocationContext);
+    }
+
+    @Override
+    public Set<RoleModel> getClientRoleMappings(ClientModel app) {
+        Set<RoleModel> result = new HashSet<RoleModel>();
+        Set<RoleModel> roles = getRoleMappings();
+
+        for (RoleModel role : roles) {
+            if (app.equals(role.getContainer())) {
+                result.add(role);
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public GroupModel getParent() {
+        if (group.getParentId() == null) return null;
+        return realm.getGroupById(group.getParentId());
+    }
+
+    @Override
+    public String getParentId() {
+        return group.getParentId();
+    }
+
+    @Override
+    public Set<GroupModel> getSubGroups() {
+        DBObject query = new QueryBuilder()
+                .and("realmId").is(realm.getId())
+                .and("parentId").is(getId())
+                .get();
+        List<MongoGroupEntity> groups = getMongoStore().loadEntities(MongoGroupEntity.class, query, invocationContext);
+
+        Set<GroupModel> subGroups = new HashSet<>();
+
+        if (groups == null) return subGroups;
+        for (MongoGroupEntity group : groups) {
+            subGroups.add(realm.getGroupById(group.getId()));
+        }
+
+        return subGroups;
+    }
+
+    @Override
+    public void setParent(GroupModel parent) {
+        if (parent == null) group.setParentId(null);
+        else {
+            group.setParentId(parent.getId());
+        }
+        updateGroup();
+
+    }
+
+    @Override
+    public void addChild(GroupModel subGroup) {
+        subGroup.setParent(this);
+        updateGroup();
+
+    }
+
+    @Override
+    public void removeChild(GroupModel subGroup) {
+        subGroup.setParent(null);
+        updateGroup();
+
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java
index a840261..18acfa9 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java
@@ -7,11 +7,13 @@ import org.keycloak.connections.mongo.api.MongoStore;
 import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
 import org.keycloak.migration.MigrationModel;
 import org.keycloak.models.ClientModel;
+import org.keycloak.models.GroupModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RealmProvider;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.mongo.keycloak.entities.MongoClientEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoGroupEntity;
 import org.keycloak.models.mongo.keycloak.entities.MongoMigrationModelEntity;
 import org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity;
 import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
@@ -122,6 +124,14 @@ public class MongoRealmProvider implements RealmProvider {
     }
 
     @Override
+    public GroupModel getGroupById(String id, RealmModel realm) {
+        MongoGroupEntity group = getMongoStore().loadEntity(MongoGroupEntity.class, id, invocationContext);
+        if (group == null) return null;
+        if (group.getRealmId() != null && !group.getRealmId().equals(realm.getId())) return null;
+        return new GroupAdapter(session, realm, group, invocationContext);
+    }
+
+    @Override
     public ClientModel getClientById(String id, RealmModel realm) {
         MongoClientEntity appData = getMongoStore().loadEntity(MongoClientEntity.class, id, invocationContext);
 
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
index 9fc9735..44758b7 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
@@ -9,6 +9,7 @@ import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.CredentialValidationOutput;
 import org.keycloak.models.FederatedIdentityModel;
+import org.keycloak.models.GroupModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RealmModel;
@@ -90,11 +91,27 @@ public class MongoUserProvider implements UserProvider {
         }
     }
 
+    @Override
+    public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
+        QueryBuilder queryBuilder = new QueryBuilder()
+                .and("realmId").is(realm.getId());
+        queryBuilder.and("groupIds").is(group.getId());
+        DBObject sort = new BasicDBObject("username", 1);
+
+        List<MongoUserEntity> users = getMongoStore().loadEntities(MongoUserEntity.class, queryBuilder.get(), sort, firstResult, maxResults, invocationContext);
+        return convertUserEntities(realm, users);
+    }
+
     protected MongoStore getMongoStore() {
         return invocationContext.getMongoStore();
     }
 
     @Override
+    public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group) {
+        return getGroupMembers(realm, group, -1, -1);
+    }
+
+    @Override
     public UserModel getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm) {
         DBObject query = new QueryBuilder()
                 .and("federatedIdentities.identityProvider").is(socialLink.getIdentityProvider())
@@ -283,6 +300,9 @@ public class MongoUserProvider implements UserProvider {
                     userModel.grantRole(application.getRole(r));
                 }
             }
+            for (GroupModel g : realm.getDefaultGroups()) {
+                userModel.joinGroup(g);
+            }
         }
 
         if (addDefaultRequiredActions) {
@@ -425,6 +445,17 @@ public class MongoUserProvider implements UserProvider {
     }
 
     @Override
+    public void preRemove(RealmModel realm, GroupModel group) {
+        // Remove this role from all users, which has it
+        DBObject query = new QueryBuilder()
+                .and("groupIds").is(group.getId())
+                .get();
+
+        DBObject pull = new BasicDBObject("$pull", query);
+        getMongoStore().updateEntities(MongoUserEntity.class, query, pull, invocationContext);
+    }
+
+    @Override
     public void preRemove(RealmModel realm, RoleModel role) {
         // Remove this role from all users, which has it
         DBObject query = new QueryBuilder()
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index f21744d..8f8d43d 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -9,6 +9,7 @@ import org.keycloak.models.AuthenticationExecutionModel;
 import org.keycloak.models.AuthenticationFlowModel;
 import org.keycloak.models.AuthenticatorConfigModel;
 import org.keycloak.models.ClientModel;
+import org.keycloak.models.GroupModel;
 import org.keycloak.models.IdentityProviderMapperModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.KeycloakSession;
@@ -34,6 +35,7 @@ import org.keycloak.models.entities.RequiredCredentialEntity;
 import org.keycloak.models.entities.UserFederationMapperEntity;
 import org.keycloak.models.entities.UserFederationProviderEntity;
 import org.keycloak.models.mongo.keycloak.entities.MongoClientEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoGroupEntity;
 import org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity;
 import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
 import org.keycloak.models.utils.KeycloakModelUtils;
@@ -608,6 +610,91 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
     }
 
     @Override
+    public GroupModel createGroup(String name) {
+        String id = KeycloakModelUtils.generateId();
+        return createGroup(id, name);
+    }
+
+    @Override
+    public GroupModel createGroup(String id, String name) {
+        if (id == null) id = KeycloakModelUtils.generateId();
+        MongoGroupEntity group = new MongoGroupEntity();
+        group.setId(id);
+        group.setName(name);
+        group.setRealmId(getId());
+
+        getMongoStore().insertEntity(group, invocationContext);
+
+        return new GroupAdapter(session, this, group, invocationContext);
+    }
+
+    @Override
+    public void addTopLevelGroup(GroupModel subGroup) {
+        subGroup.setParent(null);
+
+    }
+
+    @Override
+    public void moveGroup(GroupModel group, GroupModel toParent) {
+        if (group.getParentId() != null) {
+            group.getParent().removeChild(group);
+        }
+        group.setParent(toParent);
+        if (toParent != null) toParent.addChild(group);
+        else addTopLevelGroup(group);
+    }
+
+    @Override
+    public GroupModel getGroupById(String id) {
+        return model.getGroupById(id, this);
+    }
+
+    @Override
+    public List<GroupModel> getGroups() {
+        DBObject query = new QueryBuilder()
+                .and("realmId").is(getId())
+                .get();
+        List<MongoGroupEntity> groups = getMongoStore().loadEntities(MongoGroupEntity.class, query, invocationContext);
+
+        List<GroupModel> result = new LinkedList<>();
+
+        if (groups == null) return result;
+        for (MongoGroupEntity group : groups) {
+            result.add(model.getGroupById(group.getId(), this));
+        }
+
+        return result;
+    }
+
+    @Override
+    public List<GroupModel> getTopLevelGroups() {
+        List<GroupModel> all = getGroups();
+        Iterator<GroupModel> it = all.iterator();
+        while (it.hasNext()) {
+            GroupModel group = it.next();
+            if (group.getParentId() != null) {
+                it.remove();
+            }
+        }
+        return all;
+    }
+
+    @Override
+    public boolean removeGroup(GroupModel group) {
+        if (realm.getDefaultGroups() != null) {
+            getMongoStore().pullItemFromList(realm, "defaultGroups", group.getId(), invocationContext);
+        }
+        for (GroupModel subGroup : group.getSubGroups()) {
+            removeGroup(subGroup);
+        }
+        session.users().preRemove(this, group);
+        moveGroup(group, null);
+        return getMongoStore().removeEntity(MongoGroupEntity.class, group.getId(), invocationContext);
+    }
+
+
+
+    @Override
     public List<String> getDefaultRoles() {
         return realm.getDefaultRoles();
     }
@@ -639,6 +726,27 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
     }
 
     @Override
+    public List<GroupModel> getDefaultGroups() {
+        List<GroupModel> defaultGroups = new LinkedList<>();
+        for (String id : realm.getDefaultGroups()) {
+            defaultGroups.add(session.realms().getGroupById(id, this));
+        }
+        return defaultGroups;
+    }
+
+    @Override
+    public void addDefaultGroup(GroupModel group) {
+        getMongoStore().pushItemToList(realm, "defaultGroups", group.getId(), true, invocationContext);
+
+    }
+
+    @Override
+    public void removeDefaultGroup(GroupModel group) {
+        getMongoStore().pullItemFromList(realm, "defaultGroups", group.getId(), invocationContext);
+
+    }
+
+    @Override
     public ClientModel getClientById(String id) {
         return model.getClientById(id, this);
     }
@@ -823,9 +931,9 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
             identityProviderModel.setInternalId(entity.getInternalId());
             identityProviderModel.setConfig(entity.getConfig());
             identityProviderModel.setEnabled(entity.isEnabled());
-            identityProviderModel.setUpdateProfileFirstLoginMode(entity.getUpdateProfileFirstLoginMode());
             identityProviderModel.setTrustEmail(entity.isTrustEmail());
             identityProviderModel.setAuthenticateByDefault(entity.isAuthenticateByDefault());
+            identityProviderModel.setFirstBrokerLoginFlowId(entity.getFirstBrokerLoginFlowId());
             identityProviderModel.setStoreToken(entity.isStoreToken());
             identityProviderModel.setAddReadTokenRoleOnCreate(entity.isAddReadTokenRoleOnCreate());
 
@@ -854,11 +962,11 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
         entity.setAlias(identityProvider.getAlias());
         entity.setProviderId(identityProvider.getProviderId());
         entity.setEnabled(identityProvider.isEnabled());
-        entity.setUpdateProfileFirstLoginMode(identityProvider.getUpdateProfileFirstLoginMode());
         entity.setTrustEmail(identityProvider.isTrustEmail());
         entity.setAddReadTokenRoleOnCreate(identityProvider.isAddReadTokenRoleOnCreate());
         entity.setStoreToken(identityProvider.isStoreToken());
         entity.setAuthenticateByDefault(identityProvider.isAuthenticateByDefault());
+        entity.setFirstBrokerLoginFlowId(identityProvider.getFirstBrokerLoginFlowId());
         entity.setConfig(identityProvider.getConfig());
 
         realm.getIdentityProviders().add(entity);
@@ -882,9 +990,9 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
             if (entity.getInternalId().equals(identityProvider.getInternalId())) {
                 entity.setAlias(identityProvider.getAlias());
                 entity.setEnabled(identityProvider.isEnabled());
-                entity.setUpdateProfileFirstLoginMode(identityProvider.getUpdateProfileFirstLoginMode());
                 entity.setTrustEmail(identityProvider.isTrustEmail());
                 entity.setAuthenticateByDefault(identityProvider.isAuthenticateByDefault());
+                entity.setFirstBrokerLoginFlowId(identityProvider.getFirstBrokerLoginFlowId());
                 entity.setAddReadTokenRoleOnCreate(identityProvider.isAddReadTokenRoleOnCreate());
                 entity.setStoreToken(identityProvider.isStoreToken());
                 entity.setConfig(identityProvider.getConfig());
@@ -1148,7 +1256,13 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
     @Override
     public ClientModel getMasterAdminClient() {
         MongoClientEntity appData = getMongoStore().loadEntity(MongoClientEntity.class, realm.getMasterAdminClient(), invocationContext);
-        return appData != null ? new ClientAdapter(session, this, appData, invocationContext) : null;
+        if (appData == null) {
+            return null;
+        }
+
+        MongoRealmEntity masterRealm = getMongoStore().loadEntity(MongoRealmEntity.class, appData.getRealmId(), invocationContext);
+        RealmModel masterRealmModel = new RealmAdapter(session, masterRealm, invocationContext);
+        return new ClientAdapter(session, masterRealmModel, appData, invocationContext);
     }
 
     @Override
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
index 3235eac..83979c3 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
@@ -7,6 +7,7 @@ import com.mongodb.QueryBuilder;
 
 import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
 import org.keycloak.models.ClientModel;
+import org.keycloak.models.GroupModel;
 import org.keycloak.models.OTPPolicy;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.UserConsentModel;
@@ -451,6 +452,37 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
     }
 
     @Override
+    public Set<GroupModel> getGroups() {
+        if (user.getGroupIds() == null || user.getGroupIds().size() == 0) return Collections.EMPTY_SET;
+        Set<GroupModel> groups = new HashSet<>();
+        for (String id : user.getGroupIds()) {
+            groups.add(realm.getGroupById(id));
+        }
+        return groups;
+    }
+
+    @Override
+    public void joinGroup(GroupModel group) {
+        getMongoStore().pushItemToList(getUser(), "groupIds", group.getId(), true, invocationContext);
+
+    }
+
+    @Override
+    public void leaveGroup(GroupModel group) {
+        if (user == null || group == null) return;
+
+        getMongoStore().pullItemFromList(getUser(), "groupIds", group.getId(), invocationContext);
+
+    }
+
+    @Override
+    public boolean isMemberOf(GroupModel group) {
+        if (user.getGroupIds().contains(group.getId())) return true;
+        Set<GroupModel> groups = getGroups();
+        return KeycloakModelUtils.isMember(groups, group);
+    }
+
+    @Override
     public boolean hasRole(RoleModel role) {
         Set<RoleModel> roles = getRoleMappings();
         return KeycloakModelUtils.hasRole(roles, role);
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoGroupEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoGroupEntity.java
new file mode 100755
index 0000000..80e51a9
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoGroupEntity.java
@@ -0,0 +1,26 @@
+package org.keycloak.models.mongo.keycloak.entities;
+
+import com.mongodb.DBObject;
+import com.mongodb.QueryBuilder;
+import org.jboss.logging.Logger;
+import org.keycloak.connections.mongo.api.MongoCollection;
+import org.keycloak.connections.mongo.api.MongoField;
+import org.keycloak.connections.mongo.api.MongoIdentifiableEntity;
+import org.keycloak.connections.mongo.api.MongoStore;
+import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
+import org.keycloak.models.entities.GroupEntity;
+import org.keycloak.models.entities.RoleEntity;
+
+import java.util.List;
+
+/**
+ */
+@MongoCollection(collectionName = "groups")
+public class MongoGroupEntity extends GroupEntity implements MongoIdentifiableEntity {
+
+    private static final Logger logger = Logger.getLogger(MongoGroupEntity.class);
+
+    @Override
+    public void afterRemove(MongoStoreInvocationContext invContext) {
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRealmEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRealmEntity.java
index 8269360..78f1076 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRealmEntity.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRealmEntity.java
@@ -20,6 +20,10 @@ public class MongoRealmEntity extends RealmEntity implements MongoIdentifiableEn
                 .get();
 
         // Remove all roles of this realm
+        context.getMongoStore().removeEntities(MongoGroupEntity.class, query, true, context);
+
+
+        // Remove all roles of this realm
         context.getMongoStore().removeEntities(MongoRoleEntity.class, query, true, context);
 
         // Remove all clients of this realm
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRoleEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRoleEntity.java
index 29491f8..520f678 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRoleEntity.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRoleEntity.java
@@ -41,6 +41,18 @@ public class MongoRoleEntity extends RoleEntity implements MongoIdentifiableEnti
     public void afterRemove(MongoStoreInvocationContext invContext) {
         MongoStore mongoStore = invContext.getMongoStore();
 
+        {
+            DBObject query = new QueryBuilder()
+                    .and("roleIds").is(getId())
+                    .get();
+
+            List<MongoGroupEntity> groups = mongoStore.loadEntities(MongoGroupEntity.class, query, invContext);
+            for (MongoGroupEntity group : groups) {
+                mongoStore.pullItemFromList(group, "roleIds", getId(), invContext);
+            }
+
+        }
+
         // Remove this scope from all clients, which has it
         DBObject query = new QueryBuilder()
                 .and("scopeIds").is(getId())
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/utils/MongoModelUtils.java b/model/mongo/src/main/java/org/keycloak/models/mongo/utils/MongoModelUtils.java
index 9c2b326..eaf2216 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/utils/MongoModelUtils.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/utils/MongoModelUtils.java
@@ -4,11 +4,13 @@ import com.mongodb.DBObject;
 import com.mongodb.QueryBuilder;
 import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
 import org.keycloak.models.ClientModel;
+import org.keycloak.models.GroupModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.entities.ClientEntity;
 import org.keycloak.models.mongo.keycloak.adapters.ClientAdapter;
+import org.keycloak.models.mongo.keycloak.adapters.GroupAdapter;
 import org.keycloak.models.mongo.keycloak.adapters.UserAdapter;
 import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
 import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;

model/pom.xml 1(+0 -1)

diff --git a/model/pom.xml b/model/pom.xml
index 2f78805..0322abb 100755
--- a/model/pom.xml
+++ b/model/pom.xml
@@ -29,7 +29,6 @@
         <module>invalidation-cache</module>
         <module>jpa</module>
         <module>mongo</module>
-        <module>file</module>
         <module>sessions-infinispan</module>
     </modules>
 </project>
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientInitialAccessAdapter.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientInitialAccessAdapter.java
new file mode 100644
index 0000000..7d75335
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientInitialAccessAdapter.java
@@ -0,0 +1,69 @@
+package org.keycloak.models.sessions.infinispan;
+
+import org.infinispan.Cache;
+import org.keycloak.models.ClientInitialAccessModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.sessions.infinispan.entities.ClientInitialAccessEntity;
+import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientInitialAccessAdapter implements ClientInitialAccessModel {
+
+    private final KeycloakSession session;
+    private final InfinispanUserSessionProvider provider;
+    private final Cache<String, SessionEntity> cache;
+    private final RealmModel realm;
+    private final ClientInitialAccessEntity entity;
+
+    public ClientInitialAccessAdapter(KeycloakSession session, InfinispanUserSessionProvider provider, Cache<String, SessionEntity> cache, RealmModel realm, ClientInitialAccessEntity entity) {
+        this.session = session;
+        this.provider = provider;
+        this.cache = cache;
+        this.realm = realm;
+        this.entity = entity;
+    }
+
+    @Override
+    public String getId() {
+        return entity.getId();
+    }
+
+    @Override
+    public RealmModel getRealm() {
+        return realm;
+    }
+
+    @Override
+    public int getTimestamp() {
+        return entity.getTimestamp();
+    }
+
+    @Override
+    public int getExpiration() {
+        return entity.getExpiration();
+    }
+
+    @Override
+    public int getCount() {
+        return entity.getCount();
+    }
+
+    @Override
+    public int getRemainingCount() {
+        return entity.getRemainingCount();
+    }
+
+    @Override
+    public void decreaseRemainingCount() {
+        entity.setRemainingCount(entity.getRemainingCount() - 1);
+        update();
+    }
+
+    void update() {
+        provider.getTx().replace(cache, entity.getId(), entity);
+    }
+
+}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/ClientInitialAccessAdapter.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/ClientInitialAccessAdapter.java
new file mode 100644
index 0000000..3398eff
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/ClientInitialAccessAdapter.java
@@ -0,0 +1,55 @@
+package org.keycloak.models.sessions.infinispan.compat;
+
+import org.keycloak.models.ClientInitialAccessModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.sessions.infinispan.compat.entities.ClientInitialAccessEntity;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientInitialAccessAdapter implements ClientInitialAccessModel {
+
+    private final RealmModel realm;
+    private final ClientInitialAccessEntity entity;
+
+    public ClientInitialAccessAdapter(RealmModel realm, ClientInitialAccessEntity entity) {
+        this.realm = realm;
+        this.entity = entity;
+    }
+
+    @Override
+    public String getId() {
+        return entity.getId();
+    }
+
+    @Override
+    public RealmModel getRealm() {
+        return realm;
+    }
+
+    @Override
+    public int getTimestamp() {
+        return entity.getTimestamp();
+    }
+
+    @Override
+    public int getExpiration() {
+        return entity.getExpires();
+    }
+
+    @Override
+    public int getCount() {
+        return entity.getCount();
+    }
+
+    @Override
+    public int getRemainingCount() {
+        return entity.getRemainingCount();
+    }
+
+    @Override
+    public void decreaseRemainingCount() {
+        entity.setRemainingCount(entity.getRemainingCount() - 1);
+    }
+
+}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/entities/ClientInitialAccessEntity.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/entities/ClientInitialAccessEntity.java
new file mode 100644
index 0000000..ed0aeac
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/entities/ClientInitialAccessEntity.java
@@ -0,0 +1,68 @@
+package org.keycloak.models.sessions.infinispan.compat.entities;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientInitialAccessEntity {
+
+    private String id;
+
+    private String realmId;
+
+    private int timestamp;
+
+    private int expires;
+
+    private int count;
+
+    private int remainingCount;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getRealmId() {
+        return realmId;
+    }
+
+    public void setRealmId(String realmId) {
+        this.realmId = realmId;
+    }
+
+    public int getTimestamp() {
+        return timestamp;
+    }
+
+    public void setTimestamp(int timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    public int getExpires() {
+        return expires;
+    }
+
+    public void setExpiration(int expires) {
+        this.expires = expires;
+    }
+
+    public int getCount() {
+        return count;
+    }
+
+    public void setCount(int count) {
+        this.count = count;
+    }
+
+    public int getRemainingCount() {
+        return remainingCount;
+    }
+
+    public void setRemainingCount(int remainingCount) {
+        this.remainingCount = remainingCount;
+    }
+
+}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProvider.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProvider.java
index f45edf1..db20ef8 100755
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProvider.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProvider.java
@@ -1,19 +1,8 @@
 package org.keycloak.models.sessions.infinispan.compat;
 
-import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.ModelDuplicateException;
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.UserModel;
-import org.keycloak.models.UserSessionModel;
-import org.keycloak.models.UserSessionProvider;
-import org.keycloak.models.UsernameLoginFailureModel;
+import org.keycloak.models.*;
 import org.keycloak.models.session.UserSessionPersisterProvider;
-import org.keycloak.models.sessions.infinispan.compat.entities.ClientSessionEntity;
-import org.keycloak.models.sessions.infinispan.compat.entities.UserSessionEntity;
-import org.keycloak.models.sessions.infinispan.compat.entities.UsernameLoginFailureEntity;
-import org.keycloak.models.sessions.infinispan.compat.entities.UsernameLoginFailureKey;
+import org.keycloak.models.sessions.infinispan.compat.entities.*;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.models.utils.RealmInfoUtil;
 import org.keycloak.common.util.Time;
@@ -41,11 +30,12 @@ public class MemUserSessionProvider implements UserSessionProvider {
 
     private final ConcurrentHashMap<String, UserSessionEntity> offlineUserSessions;
     private final ConcurrentHashMap<String, ClientSessionEntity> offlineClientSessions;
+    private ConcurrentHashMap<String, ClientInitialAccessEntity> clientInitialAccess;
 
     public MemUserSessionProvider(KeycloakSession session, ConcurrentHashMap<String, UserSessionEntity> userSessions, ConcurrentHashMap<String, String> userSessionsByBrokerSessionId,
                                   ConcurrentHashMap<String, Set<String>> userSessionsByBrokerUserId, ConcurrentHashMap<String, ClientSessionEntity> clientSessions,
                                   ConcurrentHashMap<UsernameLoginFailureKey, UsernameLoginFailureEntity> loginFailures,
-                                  ConcurrentHashMap<String, UserSessionEntity> offlineUserSessions, ConcurrentHashMap<String, ClientSessionEntity> offlineClientSessions) {
+                                  ConcurrentHashMap<String, UserSessionEntity> offlineUserSessions, ConcurrentHashMap<String, ClientSessionEntity> offlineClientSessions, ConcurrentHashMap<String, ClientInitialAccessEntity> clientInitialAccess) {
         this.session = session;
         this.userSessions = userSessions;
         this.clientSessions = clientSessions;
@@ -54,6 +44,7 @@ public class MemUserSessionProvider implements UserSessionProvider {
         this.userSessionsByBrokerUserId = userSessionsByBrokerUserId;
         this.offlineUserSessions = offlineUserSessions;
         this.offlineClientSessions = offlineClientSessions;
+        this.clientInitialAccess = clientInitialAccess;
     }
 
     @Override
@@ -341,6 +332,15 @@ public class MemUserSessionProvider implements UserSessionProvider {
                 persister.removeClientSession(s.getId(), true);
             }
         }
+
+        // Remove expired initial access
+        Iterator<ClientInitialAccessEntity> iaitr = clientInitialAccess.values().iterator();
+        while (iaitr.hasNext()) {
+            ClientInitialAccessEntity e = iaitr.next();
+            if (e.getRealmId().equals(realm.getId()) && (e.getExpires() < Time.currentTime())) {
+                iaitr.remove();
+            }
+        }
     }
 
     @Override
@@ -575,6 +575,43 @@ public class MemUserSessionProvider implements UserSessionProvider {
     }
 
     @Override
+    public ClientInitialAccessModel createClientInitialAccessModel(RealmModel realm, int expiration, int count) {
+        String id = KeycloakModelUtils.generateId();
+
+        ClientInitialAccessEntity entity = new ClientInitialAccessEntity();
+        entity.setId(id);
+        entity.setRealmId(realm.getId());
+        entity.setTimestamp(Time.currentTime());
+        entity.setExpiration(expiration);
+        entity.setCount(count);
+        entity.setRemainingCount(count);
+
+        clientInitialAccess.put(id, entity);
+
+        return new ClientInitialAccessAdapter(realm, entity);
+    }
+
+    @Override
+    public ClientInitialAccessModel getClientInitialAccessModel(RealmModel realm, String id) {
+        ClientInitialAccessEntity entity = clientInitialAccess.get(id);
+        return entity != null ? new ClientInitialAccessAdapter(realm, entity) : null;
+    }
+
+    @Override
+    public void removeClientInitialAccessModel(RealmModel realm, String id) {
+        clientInitialAccess.remove(id);
+    }
+
+    @Override
+    public List<ClientInitialAccessModel> listClientInitialAccess(RealmModel realm) {
+        List<ClientInitialAccessModel> models = new LinkedList<>();
+        for (ClientInitialAccessEntity e : clientInitialAccess.values()) {
+            models.add(new ClientInitialAccessAdapter(realm, e));
+        }
+        return models;
+    }
+
+    @Override
     public void close() {
     }
 
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProviderFactory.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProviderFactory.java
index 187a33f..451fcb0 100644
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProviderFactory.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/compat/MemUserSessionProviderFactory.java
@@ -1,14 +1,12 @@
 package org.keycloak.models.sessions.infinispan.compat;
 
-import org.keycloak.Config;
 import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.UserSessionProvider;
-import org.keycloak.models.UserSessionProviderFactory;
 import org.keycloak.models.sessions.infinispan.compat.entities.ClientSessionEntity;
 import org.keycloak.models.sessions.infinispan.compat.entities.UserSessionEntity;
 import org.keycloak.models.sessions.infinispan.compat.entities.UsernameLoginFailureEntity;
 import org.keycloak.models.sessions.infinispan.compat.entities.UsernameLoginFailureKey;
+import org.keycloak.models.sessions.infinispan.compat.entities.ClientInitialAccessEntity;
 
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -29,9 +27,11 @@ public class MemUserSessionProviderFactory {
     private ConcurrentHashMap<String, UserSessionEntity> offlineUserSessions = new ConcurrentHashMap<String, UserSessionEntity>();
     private ConcurrentHashMap<String, ClientSessionEntity> offlineClientSessions = new ConcurrentHashMap<String, ClientSessionEntity>();
 
+    private ConcurrentHashMap<String, ClientInitialAccessEntity> clientInitialAccess = new ConcurrentHashMap<>();
+
     public UserSessionProvider create(KeycloakSession session) {
         return new MemUserSessionProvider(session, userSessions, userSessionsByBrokerSessionId, userSessionsByBrokerUserId, clientSessions, loginFailures,
-                offlineUserSessions, offlineClientSessions);
+                offlineUserSessions, offlineClientSessions, clientInitialAccess);
     }
 
     public void close() {
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientInitialAccessEntity.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientInitialAccessEntity.java
new file mode 100644
index 0000000..05daf1e
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientInitialAccessEntity.java
@@ -0,0 +1,48 @@
+package org.keycloak.models.sessions.infinispan.entities;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientInitialAccessEntity extends SessionEntity {
+
+    private int timestamp;
+
+    private int expires;
+
+    private int count;
+
+    private int remainingCount;
+
+    public int getTimestamp() {
+        return timestamp;
+    }
+
+    public void setTimestamp(int timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    public int getExpiration() {
+        return expires;
+    }
+
+    public void setExpiration(int expires) {
+        this.expires = expires;
+    }
+
+    public int getCount() {
+        return count;
+    }
+
+    public void setCount(int count) {
+        this.count = count;
+    }
+
+    public int getRemainingCount() {
+        return remainingCount;
+    }
+
+    public void setRemainingCount(int remainingCount) {
+        this.remainingCount = remainingCount;
+    }
+
+}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
index 9ba1587..d89f47b 100755
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
@@ -3,28 +3,10 @@ package org.keycloak.models.sessions.infinispan;
 import org.infinispan.Cache;
 import org.infinispan.distexec.mapreduce.MapReduceTask;
 import org.jboss.logging.Logger;
-import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.KeycloakTransaction;
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.UserModel;
-import org.keycloak.models.UserSessionModel;
-import org.keycloak.models.UserSessionProvider;
-import org.keycloak.models.UsernameLoginFailureModel;
+import org.keycloak.models.*;
 import org.keycloak.models.session.UserSessionPersisterProvider;
-import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
-import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
-import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
-import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
-import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
-import org.keycloak.models.sessions.infinispan.mapreduce.ClientSessionMapper;
-import org.keycloak.models.sessions.infinispan.mapreduce.FirstResultReducer;
-import org.keycloak.models.sessions.infinispan.mapreduce.LargestResultReducer;
-import org.keycloak.models.sessions.infinispan.mapreduce.SessionMapper;
-import org.keycloak.models.sessions.infinispan.mapreduce.UserLoginFailureMapper;
-import org.keycloak.models.sessions.infinispan.mapreduce.UserSessionMapper;
-import org.keycloak.models.sessions.infinispan.mapreduce.UserSessionNoteMapper;
+import org.keycloak.models.sessions.infinispan.entities.*;
+import org.keycloak.models.sessions.infinispan.mapreduce.*;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.models.utils.RealmInfoUtil;
 import org.keycloak.common.util.Time;
@@ -355,6 +337,15 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
             persister.removeClientSession(clientSessionId, true);
         }
 
+        // Remove expired client initial access
+        map = new MapReduceTask(sessionCache)
+                .mappedWith(ClientInitialAccessMapper.create(realm.getId()).expired(Time.currentTime()).emitKey())
+                .reducedWith(new FirstResultReducer())
+                .execute();
+
+        for (String id : map.keySet()) {
+            tx.remove(sessionCache, id);
+        }
     }
 
     @Override
@@ -538,11 +529,24 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
         return models;
     }
 
+    List<ClientInitialAccessModel> wrapClientInitialAccess(RealmModel realm, Collection<ClientInitialAccessEntity> entities) {
+        List<ClientInitialAccessModel> models = new LinkedList<>();
+        for (ClientInitialAccessEntity e : entities) {
+            models.add(wrap(realm, e));
+        }
+        return models;
+    }
+
     ClientSessionAdapter wrap(RealmModel realm, ClientSessionEntity entity, boolean offline) {
         Cache<String, SessionEntity> cache = getCache(offline);
         return entity != null ? new ClientSessionAdapter(session, this, cache, realm, entity, offline) : null;
     }
 
+    ClientInitialAccessAdapter wrap(RealmModel realm, ClientInitialAccessEntity entity) {
+        Cache<String, SessionEntity> cache = getCache(false);
+        return entity != null ? new ClientInitialAccessAdapter(session, this, cache, realm, entity) : null;
+    }
+
 
     UsernameLoginFailureModel wrap(LoginFailureKey key, LoginFailureEntity entity) {
         return entity != null ? new UsernameLoginFailureAdapter(this, loginFailureCache, key, entity) : null;
@@ -680,6 +684,50 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
         return wrap(clientSession.getRealm(), entity, offline);
     }
 
+    @Override
+    public ClientInitialAccessModel createClientInitialAccessModel(RealmModel realm, int expiration, int count) {
+        String id = KeycloakModelUtils.generateId();
+
+        ClientInitialAccessEntity entity = new ClientInitialAccessEntity();
+        entity.setId(id);
+        entity.setRealm(realm.getId());
+        entity.setTimestamp(Time.currentTime());
+        entity.setExpiration(expiration);
+        entity.setCount(count);
+        entity.setRemainingCount(count);
+
+        tx.put(sessionCache, id, entity);
+
+        return wrap(realm, entity);
+    }
+
+    @Override
+    public ClientInitialAccessModel getClientInitialAccessModel(RealmModel realm, String id) {
+        Cache<String, SessionEntity> cache = getCache(false);
+        ClientInitialAccessEntity entity = (ClientInitialAccessEntity) cache.get(id);
+
+        // If created in this transaction
+        if (entity == null) {
+            entity = (ClientInitialAccessEntity) tx.get(cache, id);
+        }
+
+        return wrap(realm, entity);
+    }
+
+    @Override
+    public void removeClientInitialAccessModel(RealmModel realm, String id) {
+        tx.remove(getCache(false), id);
+    }
+
+    @Override
+    public List<ClientInitialAccessModel> listClientInitialAccess(RealmModel realm) {
+        Map<String, ClientInitialAccessEntity> entities = new MapReduceTask(sessionCache)
+                .mappedWith(ClientInitialAccessMapper.create(realm.getId()))
+                .reducedWith(new FirstResultReducer())
+                .execute();
+        return wrapClientInitialAccess(realm, entities.values());
+    }
+
     class InfinispanKeycloakTransaction implements KeycloakTransaction {
 
         private boolean active;
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientInitialAccessMapper.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientInitialAccessMapper.java
new file mode 100644
index 0000000..98dab2f
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientInitialAccessMapper.java
@@ -0,0 +1,79 @@
+package org.keycloak.models.sessions.infinispan.mapreduce;
+
+import org.infinispan.distexec.mapreduce.Collector;
+import org.infinispan.distexec.mapreduce.Mapper;
+import org.keycloak.models.sessions.infinispan.entities.ClientInitialAccessEntity;
+import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
+
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientInitialAccessMapper implements Mapper<String, SessionEntity, String, Object>, Serializable {
+
+    public ClientInitialAccessMapper(String realm) {
+        this.realm = realm;
+    }
+
+    private enum EmitValue {
+        KEY, ENTITY
+    }
+
+    private String realm;
+
+    private EmitValue emit = EmitValue.ENTITY;
+
+    private Integer expired;
+
+    public static ClientInitialAccessMapper create(String realm) {
+        return new ClientInitialAccessMapper(realm);
+    }
+
+    public ClientInitialAccessMapper emitKey() {
+        emit = EmitValue.KEY;
+        return this;
+    }
+
+    public ClientInitialAccessMapper expired(int time) {
+        this.expired = time;
+        return this;
+    }
+
+    @Override
+    public void map(String key, SessionEntity e, Collector collector) {
+        if (!realm.equals(e.getRealm())) {
+            return;
+        }
+
+        if (!(e instanceof ClientInitialAccessEntity)) {
+            return;
+        }
+
+        ClientInitialAccessEntity entity = (ClientInitialAccessEntity) e;
+
+        boolean include = false;
+
+        if (expired != null) {
+            if (entity.getRemainingCount() <= 0) {
+                include = true;
+            } else if (entity.getExpiration() > 0 && (entity.getTimestamp() + entity.getExpiration()) < expired) {
+                include = true;
+            }
+        } else {
+            include = true;
+        }
+
+        if (include) {
+            switch (emit) {
+                case KEY:
+                    collector.emit(key, key);
+                    break;
+                case ENTITY:
+                    collector.emit(key, entity);
+                    break;
+            }
+        }
+    }
+
+}

pom.xml 33(+17 -16)

diff --git a/pom.xml b/pom.xml
index 12a346e..c137e59 100755
--- a/pom.xml
+++ b/pom.xml
@@ -48,8 +48,8 @@
         <dom4j.version>1.6.1</dom4j.version>
         <xml-apis.version>1.4.01</xml-apis.version>
         <slf4j.version>1.7.7</slf4j.version>
-        <wildfly.version>9.0.1.Final</wildfly.version>
-        <wildfly.core.version>1.0.1.Final</wildfly.core.version>
+        <wildfly.version>9.0.2.Final</wildfly.version>
+        <wildfly.core.version>1.0.2.Final</wildfly.core.version>
         <wildfly.build-tools.version>1.0.0.Final</wildfly.build-tools.version>
 
         <!-- this is EAP 6.4 alpha, publicly available -->
@@ -76,6 +76,7 @@
         <log4j.version>1.2.17</log4j.version>
         <greenmail.version>1.3.1b</greenmail.version>
         <xmlsec.version>1.5.1</xmlsec.version>
+        <aesh.version>0.66</aesh.version>
 
         <enforcer.plugin.version>1.4</enforcer.plugin.version>
         <jboss.as.plugin.version>7.5.Final</jboss.as.plugin.version>
@@ -135,7 +136,7 @@
     <modules>
         <module>common</module>
         <module>core</module>
-        <module>client-api</module>
+        <module>client-registration</module>
         <module>connections</module>
         <module>dependencies</module>
         <module>events</module>
@@ -580,6 +581,11 @@
                 <artifactId>pax-web-runtime</artifactId>
                 <version>${pax.web.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.jboss.aesh</groupId>
+                <artifactId>aesh</artifactId>
+                <version>${aesh.version}</version>
+            </dependency>
 
             <!-- keycloak -->
             <dependency>
@@ -599,11 +605,6 @@
             </dependency>
             <dependency>
                 <groupId>org.keycloak</groupId>
-                <artifactId>keycloak-connections-file</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>org.keycloak</groupId>
                 <artifactId>keycloak-connections-infinispan</artifactId>
                 <version>${project.version}</version>
             </dependency>
@@ -629,6 +630,11 @@
             </dependency>
             <dependency>
                 <groupId>org.keycloak</groupId>
+                <artifactId>keycloak-client-registration-api</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.keycloak</groupId>
                 <artifactId>keycloak-connections-mongo-update</artifactId>
                 <version>${project.version}</version>
             </dependency>
@@ -838,7 +844,7 @@
                 <groupId>org.keycloak</groupId>
                 <artifactId>keycloak-wf9-server-subsystem</artifactId>
                 <version>${project.version}</version>
-            </dependency>            
+            </dependency>
             <dependency>
                 <groupId>org.keycloak</groupId>
                 <artifactId>keycloak-subsystem</artifactId>
@@ -961,11 +967,6 @@
             </dependency>
             <dependency>
                 <groupId>org.keycloak</groupId>
-                <artifactId>keycloak-model-file</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>org.keycloak</groupId>
                 <artifactId>keycloak-invalidation-cache-infinispan</artifactId>
                 <version>${project.version}</version>
             </dependency>
@@ -1441,7 +1442,7 @@
                     <groupId>org.wildfly.build</groupId>
                     <artifactId>wildfly-feature-pack-build-maven-plugin</artifactId>
                     <version>${wildfly.build-tools.version}</version>
-                </plugin> 
+                </plugin>
                 <plugin>
                     <groupId>org.wildfly.build</groupId>
                     <artifactId>wildfly-server-provisioning-maven-plugin</artifactId>
@@ -1462,7 +1463,7 @@
                                     <requireMavenVersion>
                                         <version>3.1.1</version>
                                     </requireMavenVersion>
-                                </rules>    
+                                </rules>
                             </configuration>
                         </execution>
                     </executions>
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticator.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticator.java
index fb66a70..39f026a 100755
--- a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticator.java
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticator.java
@@ -22,6 +22,7 @@ import org.keycloak.saml.SAML2LogoutResponseBuilder;
 import org.keycloak.saml.SAMLRequestParser;
 import org.keycloak.saml.SignatureAlgorithm;
 import org.keycloak.saml.common.constants.GeneralConstants;
+import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
 import org.keycloak.saml.common.exceptions.ProcessingException;
 import org.keycloak.saml.common.util.Base64;
 import org.keycloak.saml.processing.api.saml.v2.sig.SAML2Signature;
@@ -33,6 +34,7 @@ import org.keycloak.common.util.MultivaluedHashMap;
 import org.w3c.dom.Document;
 import org.w3c.dom.Node;
 
+import java.net.URI;
 import java.security.PublicKey;
 import java.security.Signature;
 import java.util.HashSet;
@@ -312,7 +314,9 @@ public abstract class SamlAuthenticator {
         }
 
 
-        final SamlPrincipal principal = new SamlPrincipal(principalName, principalName, subjectNameID.getFormat().toString(), attributes, friendlyAttributes);
+        URI nameFormat = subjectNameID.getFormat();
+        String nameFormatString = nameFormat == null ?  JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get() : nameFormat.toString();
+        final SamlPrincipal principal = new SamlPrincipal(principalName, principalName, nameFormatString, attributes, friendlyAttributes);
         String index = authn == null ? null : authn.getSessionIndex();
         final String sessionIndex = index;
         SamlSession account = new SamlSession(principal, roles, sessionIndex);
diff --git a/saml/client-adapter/jetty/jetty8.1/pom.xml b/saml/client-adapter/jetty/jetty8.1/pom.xml
index 937b8ee..1e888f5 100755
--- a/saml/client-adapter/jetty/jetty8.1/pom.xml
+++ b/saml/client-adapter/jetty/jetty8.1/pom.xml
@@ -54,21 +54,21 @@
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-server</artifactId>
             <version>${jetty9.version}</version>
-            <scope>compile</scope>
+            <scope>provided</scope>
         </dependency>
 
         <dependency>
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-util</artifactId>
             <version>${jetty9.version}</version>
-            <scope>compile</scope>
+            <scope>provided</scope>
         </dependency>
 
         <dependency>
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-security</artifactId>
             <version>${jetty9.version}</version>
-            <scope>compile</scope>
+            <scope>provided</scope>
         </dependency>
 
 		<dependency>
diff --git a/saml/client-adapter/jetty/jetty9.1/pom.xml b/saml/client-adapter/jetty/jetty9.1/pom.xml
index f9bc11b..b8ca67b 100755
--- a/saml/client-adapter/jetty/jetty9.1/pom.xml
+++ b/saml/client-adapter/jetty/jetty9.1/pom.xml
@@ -69,21 +69,21 @@
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-server</artifactId>
             <version>${jetty9.version}</version>
-            <scope>compile</scope>
+            <scope>provided</scope>
         </dependency>
 
         <dependency>
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-util</artifactId>
             <version>${jetty9.version}</version>
-            <scope>compile</scope>
+            <scope>provided</scope>
         </dependency>
 
         <dependency>
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-security</artifactId>
             <version>${jetty9.version}</version>
-            <scope>compile</scope>
+            <scope>provided</scope>
         </dependency>
 
 		<dependency>
diff --git a/saml/client-adapter/jetty/jetty9.2/pom.xml b/saml/client-adapter/jetty/jetty9.2/pom.xml
index d7dc8b8..9382c4a 100755
--- a/saml/client-adapter/jetty/jetty9.2/pom.xml
+++ b/saml/client-adapter/jetty/jetty9.2/pom.xml
@@ -69,21 +69,21 @@
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-server</artifactId>
             <version>${jetty9.version}</version>
-            <scope>compile</scope>
+            <scope>provided</scope>
         </dependency>
 
         <dependency>
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-util</artifactId>
             <version>${jetty9.version}</version>
-            <scope>compile</scope>
+            <scope>provided</scope>
         </dependency>
 
         <dependency>
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-security</artifactId>
             <version>${jetty9.version}</version>
-            <scope>compile</scope>
+            <scope>provided</scope>
         </dependency>
 
 		<dependency>
diff --git a/saml/client-adapter/jetty/jetty-core/pom.xml b/saml/client-adapter/jetty/jetty-core/pom.xml
index 52cc273..87bf21a 100755
--- a/saml/client-adapter/jetty/jetty-core/pom.xml
+++ b/saml/client-adapter/jetty/jetty-core/pom.xml
@@ -59,21 +59,21 @@
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-server</artifactId>
             <version>${jetty9.version}</version>
-            <scope>compile</scope>
+            <scope>provided</scope>
         </dependency>
 
         <dependency>
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-util</artifactId>
             <version>${jetty9.version}</version>
-            <scope>compile</scope>
+            <scope>provided</scope>
         </dependency>
 
         <dependency>
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-security</artifactId>
             <version>${jetty9.version}</version>
-            <scope>compile</scope>
+            <scope>provided</scope>
         </dependency>
 
 		<dependency>
diff --git a/saml/saml-core/pom.xml b/saml/saml-core/pom.xml
index 552f7be..5a1c01b 100755
--- a/saml/saml-core/pom.xml
+++ b/saml/saml-core/pom.xml
@@ -32,11 +32,6 @@
             <groupId>org.apache.santuario</groupId>
             <artifactId>xmlsec</artifactId>
         </dependency>
-        <dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit</artifactId>
-            <scope>test</scope>
-        </dependency>
     </dependencies>
     <build>
         <resources>
diff --git a/saml/saml-core/src/main/java/org/keycloak/saml/common/parsers/AbstractParser.java b/saml/saml-core/src/main/java/org/keycloak/saml/common/parsers/AbstractParser.java
index 3b5d545..dc25f17 100755
--- a/saml/saml-core/src/main/java/org/keycloak/saml/common/parsers/AbstractParser.java
+++ b/saml/saml-core/src/main/java/org/keycloak/saml/common/parsers/AbstractParser.java
@@ -76,6 +76,11 @@ public abstract class AbstractParser implements ParserNamespaceSupport {
      * @throws {@link IllegalArgumentException} when the configStream is null
      */
     public Object parse(InputStream configStream) throws ParsingException {
+        XMLEventReader xmlEventReader = createEventReader(configStream);
+        return parse(xmlEventReader);
+    }
+
+    public XMLEventReader createEventReader(InputStream configStream) throws ParsingException {
         if (configStream == null)
             throw logger.nullArgumentError("InputStream");
 
@@ -105,7 +110,7 @@ public abstract class AbstractParser implements ParserNamespaceSupport {
             throw logger.parserException(e);
         }
 
-        return parse(xmlEventReader);
+        return xmlEventReader;
     }
 
     private ClassLoader getTCCL() {
diff --git a/saml/saml-core/src/main/java/org/keycloak/saml/common/util/StringUtil.java b/saml/saml-core/src/main/java/org/keycloak/saml/common/util/StringUtil.java
index 9157982..3db64c2 100755
--- a/saml/saml-core/src/main/java/org/keycloak/saml/common/util/StringUtil.java
+++ b/saml/saml-core/src/main/java/org/keycloak/saml/common/util/StringUtil.java
@@ -180,36 +180,6 @@ public class StringUtil {
         return map;
     }
 
-    /**
-     * Given a masked password {@link String}, decode it
-     *
-     * @param maskedString a password string that is masked
-     * @param salt Salt
-     * @param iterationCount Iteration Count
-     *
-     * @return Decoded String
-     *
-     * @throws Exception
-     */
-    public static String decode(String maskedString, String salt, int iterationCount) throws Exception {
-        String pbeAlgo = PicketLinkCommonConstants.PBE_ALGORITHM;
-        if (maskedString.startsWith(PicketLinkCommonConstants.PASS_MASK_PREFIX)) {
-            // Create the PBE secret key
-            SecretKeyFactory factory = SecretKeyFactory.getInstance(pbeAlgo);
-
-            char[] password = "somearbitrarycrazystringthatdoesnotmatter".toCharArray();
-            PBEParameterSpec cipherSpec = new PBEParameterSpec(salt.getBytes(), iterationCount);
-            PBEKeySpec keySpec = new PBEKeySpec(password);
-            SecretKey cipherKey = factory.generateSecret(keySpec);
-
-            maskedString = maskedString.substring(PicketLinkCommonConstants.PASS_MASK_PREFIX.length());
-            String decodedValue = PBEUtils.decode64(maskedString, pbeAlgo, cipherKey, cipherSpec);
-
-            maskedString = decodedValue;
-        }
-        return maskedString;
-    }
-
     public static String[] split(String toSplit, String delimiter) {
         if (delimiter.length() != 1) {
             throw new IllegalArgumentException("Delimiter can only be one character in length");
diff --git a/saml/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLAssertionWriter.java b/saml/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLAssertionWriter.java
index af646e2..23a90d3 100755
--- a/saml/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLAssertionWriter.java
+++ b/saml/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLAssertionWriter.java
@@ -136,7 +136,7 @@ public class SAMLAssertionWriter extends BaseWriter {
         if (statements != null) {
             for (StatementAbstractType statement : statements) {
                 if (statement instanceof AuthnStatementType) {
-                    write((AuthnStatementType) statement);
+                    write((AuthnStatementType) statement, false);
                 } else if (statement instanceof AttributeStatementType) {
                     write((AttributeStatementType) statement);
                 } else
@@ -188,8 +188,12 @@ public class SAMLAssertionWriter extends BaseWriter {
      *
      * @throws ProcessingException
      */
-    public void write(AuthnStatementType authnStatement) throws ProcessingException {
+    public void write(AuthnStatementType authnStatement, boolean includeNamespace) throws ProcessingException {
         StaxUtil.writeStartElement(writer, ASSERTION_PREFIX, JBossSAMLConstants.AUTHN_STATEMENT.get(), ASSERTION_NSURI.get());
+        if (includeNamespace) {
+            StaxUtil.writeNameSpace(writer, ASSERTION_PREFIX, ASSERTION_NSURI.get());
+            StaxUtil.writeDefaultNameSpace(writer, ASSERTION_NSURI.get());
+        }
 
         XMLGregorianCalendar authnInstant = authnStatement.getAuthnInstant();
         if (authnInstant != null) {
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java
new file mode 100644
index 0000000..1ee3cd2
--- /dev/null
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java
@@ -0,0 +1,35 @@
+package org.keycloak.protocol.saml.clientregistration;
+
+import org.keycloak.exportimport.ClientDescriptionConverter;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.protocol.saml.EntityDescriptorDescriptionConverter;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.services.clientregistration.AbstractClientRegistrationProvider;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.net.URI;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class EntityDescriptorClientRegistrationProvider extends AbstractClientRegistrationProvider {
+
+    public EntityDescriptorClientRegistrationProvider(KeycloakSession session) {
+        super(session);
+    }
+
+    @POST
+    @Consumes(MediaType.APPLICATION_XML)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response createSaml(String descriptor) {
+        ClientRepresentation client = session.getProvider(ClientDescriptionConverter.class, EntityDescriptorDescriptionConverter.ID).convertToInternal(descriptor);
+        client = create(client);
+        URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(client.getClientId()).build();
+        return Response.created(uri).entity(client).build();
+    }
+
+}
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/EntityDescriptorDescriptionConverter.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/EntityDescriptorDescriptionConverter.java
index da99613..42ff3ee 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/EntityDescriptorDescriptionConverter.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/EntityDescriptorDescriptionConverter.java
@@ -33,6 +33,8 @@ import java.util.Map;
  */
 public class EntityDescriptorDescriptionConverter implements ClientDescriptionConverter, ClientDescriptionConverterFactory {
 
+    public static final String ID = "saml2-entity-descriptor";
+
     @Override
     public boolean isSupported(String description) {
         description = description.trim();
@@ -161,7 +163,7 @@ public class EntityDescriptorDescriptionConverter implements ClientDescriptionCo
 
     @Override
     public String getId() {
-        return "saml2-entity-descriptor";
+        return ID;
     }
 
 }
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/GroupMembershipMapper.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/GroupMembershipMapper.java
new file mode 100755
index 0000000..2253e5c
--- /dev/null
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/GroupMembershipMapper.java
@@ -0,0 +1,157 @@
+package org.keycloak.protocol.saml.mappers;
+
+import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
+import org.keycloak.dom.saml.v2.assertion.AttributeType;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.protocol.ProtocolMapper;
+import org.keycloak.protocol.saml.SamlProtocol;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.services.managers.ClientSessionCode;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class GroupMembershipMapper extends AbstractSAMLProtocolMapper implements SAMLAttributeStatementMapper {
+    public static final String PROVIDER_ID = "saml-group-membership-mapper";
+    public static final String SINGLE_GROUP_ATTRIBUTE = "single";
+
+    private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
+
+    static {
+        ProviderConfigProperty property;
+        property = new ProviderConfigProperty();
+        property.setName(AttributeStatementHelper.SAML_ATTRIBUTE_NAME);
+        property.setLabel("Group attribute name");
+        property.setDefaultValue("member");
+        property.setHelpText("Name of the SAML attribute you want to put your groups into.  i.e. 'member', 'memberOf'.");
+        configProperties.add(property);
+        property = new ProviderConfigProperty();
+        property.setName(AttributeStatementHelper.FRIENDLY_NAME);
+        property.setLabel(AttributeStatementHelper.FRIENDLY_NAME_LABEL);
+        property.setHelpText(AttributeStatementHelper.FRIENDLY_NAME_HELP_TEXT);
+        configProperties.add(property);
+        property = new ProviderConfigProperty();
+        property.setName(AttributeStatementHelper.SAML_ATTRIBUTE_NAMEFORMAT);
+        property.setLabel("SAML Attribute NameFormat");
+        property.setHelpText("SAML Attribute NameFormat.  Can be basic, URI reference, or unspecified.");
+        List<String> types = new ArrayList(3);
+        types.add(AttributeStatementHelper.BASIC);
+        types.add(AttributeStatementHelper.URI_REFERENCE);
+        types.add(AttributeStatementHelper.UNSPECIFIED);
+        property.setType(ProviderConfigProperty.LIST_TYPE);
+        property.setDefaultValue(types);
+        configProperties.add(property);
+        property = new ProviderConfigProperty();
+        property.setName(SINGLE_GROUP_ATTRIBUTE);
+        property.setLabel("Single Group Attribute");
+        property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
+        property.setDefaultValue("true");
+        property.setHelpText("If true, all groups will be stored under one attribute with multiple attribute values.");
+        configProperties.add(property);
+        property = new ProviderConfigProperty();
+        property.setName("full.path");
+        property.setLabel("Full group path");
+        property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
+        property.setDefaultValue("true");
+        property.setHelpText("Include full path to group i.e. /top/level1/level2, false will just specify the group name");
+        configProperties.add(property);
+
+
+    }
+
+
+    @Override
+    public String getDisplayCategory() {
+        return "Group Mapper";
+    }
+
+    @Override
+    public String getDisplayType() {
+        return "Group list";
+    }
+
+    @Override
+    public String getHelpText() {
+        return "Group names are stored in an attribute value.  There is either one attribute with multiple attribute values, or an attribute per group name depending on how you configure it.  You can also specify the attribute name i.e. 'member' or 'memberOf' being examples.";
+    }
+
+    @Override
+    public List<ProviderConfigProperty> getConfigProperties() {
+        return configProperties;
+    }
+
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+
+    public static boolean useFullPath(ProtocolMapperModel mappingModel) {
+        return "true".equals(mappingModel.getConfig().get("full.path"));
+    }
+
+
+    @Override
+    public void transformAttributeStatement(AttributeStatementType attributeStatement, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
+        String single = mappingModel.getConfig().get(SINGLE_GROUP_ATTRIBUTE);
+        boolean singleAttribute = Boolean.parseBoolean(single);
+
+        boolean fullPath = useFullPath(mappingModel);
+        AttributeType singleAttributeType = null;
+        for (GroupModel group : userSession.getUser().getGroups()) {
+            String groupName;
+            if (fullPath) {
+                groupName = ModelToRepresentation.buildGroupPath(group);
+            } else {
+                groupName = group.getName();
+            }
+            AttributeType attributeType = null;
+            if (singleAttribute) {
+                if (singleAttributeType == null) {
+                    singleAttributeType = AttributeStatementHelper.createAttributeType(mappingModel);
+                    attributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(singleAttributeType));
+                }
+                attributeType = singleAttributeType;
+            } else {
+                attributeType = AttributeStatementHelper.createAttributeType(mappingModel);
+                attributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(attributeType));
+            }
+            attributeType.addAttributeValue(groupName);
+        }
+    }
+
+    public static ProtocolMapperModel create(String name, String samlAttributeName, String nameFormat, String friendlyName, boolean singleAttribute) {
+        ProtocolMapperModel mapper = new ProtocolMapperModel();
+        mapper.setName(name);
+        mapper.setProtocolMapper(PROVIDER_ID);
+        mapper.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
+        mapper.setConsentRequired(false);
+        Map<String, String> config = new HashMap<String, String>();
+        config.put(AttributeStatementHelper.SAML_ATTRIBUTE_NAME, samlAttributeName);
+        if (friendlyName != null) {
+            config.put(AttributeStatementHelper.FRIENDLY_NAME, friendlyName);
+        }
+        if (nameFormat != null) {
+            config.put(AttributeStatementHelper.SAML_ATTRIBUTE_NAMEFORMAT, nameFormat);
+        }
+        config.put(SINGLE_GROUP_ATTRIBUTE, Boolean.toString(singleAttribute));
+        mapper.setConfig(config);
+
+        return mapper;
+    }
+
+}
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/SAMLGroupNameMapper.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/SAMLGroupNameMapper.java
new file mode 100755
index 0000000..87a48c9
--- /dev/null
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/SAMLGroupNameMapper.java
@@ -0,0 +1,12 @@
+package org.keycloak.protocol.saml.mappers;
+
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.ProtocolMapperModel;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface SAMLGroupNameMapper {
+    public String mapName(ProtocolMapperModel model, GroupModel group);
+}
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/UserAttributeStatementMapper.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/UserAttributeStatementMapper.java
index dc80760..8f5e3b8 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/UserAttributeStatementMapper.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/UserAttributeStatementMapper.java
@@ -5,6 +5,7 @@ import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.ProtocolMapperUtils;
 import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
 import org.keycloak.provider.ProviderConfigProperty;
@@ -62,7 +63,7 @@ public class UserAttributeStatementMapper extends AbstractSAMLProtocolMapper imp
     public void transformAttributeStatement(AttributeStatementType attributeStatement, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
         UserModel user = userSession.getUser();
         String attributeName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_ATTRIBUTE);
-        String attributeValue = user.getFirstAttribute(attributeName);
+        String attributeValue = KeycloakModelUtils.resolveFirstAttribute(user, attributeName);
         if (attributeValue == null) return;
         AttributeStatementHelper.addAttribute(attributeStatement, mappingModel, attributeValue);
 
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
index 1e3f5db..c370d67 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
@@ -511,6 +511,7 @@ public class SamlService {
         processor.setClientSession(clientSession)
                 .setFlowPath(LoginActionsService.AUTHENTICATE_PATH)
                 .setFlowId(flowId)
+                .setBrowserFlow(true)
                 .setConnection(clientConnection)
                 .setEventBuilder(event)
                 .setProtector(authManager.getProtector())
diff --git a/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper b/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper
index 163fed5..df62632 100755
--- a/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper
+++ b/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper
@@ -5,5 +5,6 @@ org.keycloak.protocol.saml.mappers.HardcodedAttributeMapper
 org.keycloak.protocol.saml.mappers.UserAttributeStatementMapper
 org.keycloak.protocol.saml.mappers.UserPropertyAttributeStatementMapper
 org.keycloak.protocol.saml.mappers.UserSessionNoteStatementMapper
+org.keycloak.protocol.saml.mappers.GroupMembershipMapper
 
 
diff --git a/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.services.clientregistration.ClientRegistrationProviderFactory b/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.services.clientregistration.ClientRegistrationProviderFactory
new file mode 100644
index 0000000..e4f8117
--- /dev/null
+++ b/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.services.clientregistration.ClientRegistrationProviderFactory
@@ -0,0 +1 @@
+org.keycloak.protocol.saml.clientregistration.EntityDescriptorClientRegistrationProviderFactory
\ No newline at end of file
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationFlowError.java b/services/src/main/java/org/keycloak/authentication/AuthenticationFlowError.java
index 8fd0faa..db3db37 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationFlowError.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationFlowError.java
@@ -22,5 +22,9 @@ public enum AuthenticationFlowError {
     CLIENT_NOT_FOUND,
     CLIENT_DISABLED,
     CLIENT_CREDENTIALS_SETUP_REQUIRED,
-    INVALID_CLIENT_CREDENTIALS
+    INVALID_CLIENT_CREDENTIALS,
+
+    IDENTITY_PROVIDER_NOT_FOUND,
+    IDENTITY_PROVIDER_DISABLED,
+    IDENTITY_PROVIDER_ERROR
 }
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/AbstractIdpAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/AbstractIdpAuthenticator.java
new file mode 100644
index 0000000..b7c7663
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/AbstractIdpAuthenticator.java
@@ -0,0 +1,118 @@
+package org.keycloak.authentication.authenticators.broker;
+
+import javax.ws.rs.core.Response;
+
+import org.keycloak.authentication.AuthenticationFlowContext;
+import org.keycloak.authentication.AuthenticationFlowError;
+import org.keycloak.authentication.AuthenticationFlowException;
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.authentication.authenticators.broker.util.ExistingUserInfo;
+import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
+import org.keycloak.broker.provider.BrokeredIdentityContext;
+import org.keycloak.events.Errors;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.services.messages.Messages;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public abstract class AbstractIdpAuthenticator implements Authenticator {
+
+    // The clientSession note encapsulating all the BrokeredIdentityContext info. When this note is in clientSession, we know that firstBrokerLogin flow is in progress
+    public static final String BROKERED_CONTEXT_NOTE = "BROKERED_CONTEXT";
+
+    // The clientSession note with all the info about existing user
+    public static final String EXISTING_USER_INFO = "EXISTING_USER_INFO";
+
+    // The clientSession note flag to indicate that email provided by identityProvider was changed on updateProfile page
+    public static final String UPDATE_PROFILE_EMAIL_CHANGED = "UPDATE_PROFILE_EMAIL_CHANGED";
+
+    // The clientSession note flag to indicate if re-authentication after first broker login happened in different browser window. This can happen for example during email verification
+    public static final String IS_DIFFERENT_BROWSER = "IS_DIFFERENT_BROWSER";
+
+    // The clientSession note flag to indicate that updateProfile page will be always displayed even if "updateProfileOnFirstLogin" is off
+    public static final String ENFORCE_UPDATE_PROFILE = "ENFORCE_UPDATE_PROFILE";
+
+    // clientSession.note flag specifies if we imported new user to keycloak (true) or we just linked to an existing keycloak user (false)
+    public static final String BROKER_REGISTERED_NEW_USER = "BROKER_REGISTERED_NEW_USER";
+
+
+    @Override
+    public void authenticate(AuthenticationFlowContext context) {
+        ClientSessionModel clientSession = context.getClientSession();
+
+        SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromClientSession(clientSession);
+        if (serializedCtx == null) {
+            throw new AuthenticationFlowException("Not found serialized context in clientSession", AuthenticationFlowError.IDENTITY_PROVIDER_ERROR);
+        }
+        BrokeredIdentityContext brokerContext = serializedCtx.deserialize(context.getSession(), clientSession);
+
+        if (!brokerContext.getIdpConfig().isEnabled()) {
+            sendFailureChallenge(context, Errors.IDENTITY_PROVIDER_ERROR, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, AuthenticationFlowError.IDENTITY_PROVIDER_ERROR);
+        }
+
+        authenticateImpl(context, serializedCtx, brokerContext);
+    }
+
+    @Override
+    public void action(AuthenticationFlowContext context) {
+        ClientSessionModel clientSession = context.getClientSession();
+
+        SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromClientSession(clientSession);
+        if (serializedCtx == null) {
+            throw new AuthenticationFlowException("Not found serialized context in clientSession", AuthenticationFlowError.IDENTITY_PROVIDER_ERROR);
+        }
+        BrokeredIdentityContext brokerContext = serializedCtx.deserialize(context.getSession(), clientSession);
+
+        if (!brokerContext.getIdpConfig().isEnabled()) {
+            sendFailureChallenge(context, Errors.IDENTITY_PROVIDER_ERROR, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, AuthenticationFlowError.IDENTITY_PROVIDER_ERROR);
+        }
+
+        actionImpl(context, serializedCtx, brokerContext);
+    }
+
+    protected abstract void authenticateImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext);
+    protected abstract void actionImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext);
+
+    protected void sendFailureChallenge(AuthenticationFlowContext context, String eventError, String errorMessage, AuthenticationFlowError flowError) {
+        context.getEvent().user(context.getUser())
+                .error(eventError);
+        Response challengeResponse = context.form()
+                .setError(errorMessage)
+                .createErrorPage();
+        context.failureChallenge(flowError, challengeResponse);
+    }
+
+    @Override
+    public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
+    }
+
+    @Override
+    public void close() {
+
+    }
+
+    public static UserModel getExistingUser(KeycloakSession session, RealmModel realm, ClientSessionModel clientSession) {
+        String existingUserId = clientSession.getNote(EXISTING_USER_INFO);
+        if (existingUserId == null) {
+            throw new AuthenticationFlowException("Unexpected state. There is no existing duplicated user identified in ClientSession",
+                    AuthenticationFlowError.INTERNAL_ERROR);
+        }
+
+        ExistingUserInfo duplication = ExistingUserInfo.deserialize(existingUserId);
+
+        UserModel existingUser = session.users().getUserById(duplication.getExistingUserId(), realm);
+        if (existingUser == null) {
+            throw new AuthenticationFlowException("User with ID '" + existingUserId + "' not found.", AuthenticationFlowError.INVALID_USER);
+        }
+
+        if (!existingUser.isEnabled()) {
+            throw new AuthenticationFlowException("User with ID '" + existingUserId + "', username '" + existingUser.getUsername() + "' disabled.", AuthenticationFlowError.USER_DISABLED);
+        }
+
+        return existingUser;
+    }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpConfirmLinkAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpConfirmLinkAuthenticator.java
new file mode 100644
index 0000000..6d6281d
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpConfirmLinkAuthenticator.java
@@ -0,0 +1,73 @@
+package org.keycloak.authentication.authenticators.broker;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+
+import org.jboss.logging.Logger;
+import org.keycloak.authentication.AuthenticationFlowContext;
+import org.keycloak.authentication.AuthenticationFlowError;
+import org.keycloak.authentication.AuthenticationFlowException;
+import org.keycloak.authentication.authenticators.broker.util.ExistingUserInfo;
+import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
+import org.keycloak.broker.provider.BrokeredIdentityContext;
+import org.keycloak.login.LoginFormsProvider;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.services.messages.Messages;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class IdpConfirmLinkAuthenticator extends AbstractIdpAuthenticator {
+
+    protected static Logger logger = Logger.getLogger(IdpConfirmLinkAuthenticator.class);
+
+    @Override
+    protected void authenticateImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) {
+        ClientSessionModel clientSession = context.getClientSession();
+
+        String existingUserInfo = clientSession.getNote(EXISTING_USER_INFO);
+        if (existingUserInfo == null) {
+            logger.warnf("No duplication detected.");
+            context.attempted();
+            return;
+        }
+
+        ExistingUserInfo duplicationInfo = ExistingUserInfo.deserialize(existingUserInfo);
+        Response challenge = context.form()
+                .setStatus(Response.Status.OK)
+                .setAttribute(LoginFormsProvider.IDENTITY_PROVIDER_BROKER_CONTEXT, brokerContext)
+                .setError(Messages.FEDERATED_IDENTITY_CONFIRM_LINK_MESSAGE, duplicationInfo.getDuplicateAttributeName(), duplicationInfo.getDuplicateAttributeValue())
+                .createIdpLinkConfirmLinkPage();
+        context.challenge(challenge);
+    }
+
+    @Override
+    protected void actionImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) {
+        MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
+
+        String action = formData.getFirst("submitAction");
+        if (action != null && action.equals("updateProfile")) {
+            context.getClientSession().setNote(ENFORCE_UPDATE_PROFILE, "true");
+            context.getClientSession().removeNote(EXISTING_USER_INFO);
+            context.resetFlow();
+        } else if (action != null && action.equals("linkAccount")) {
+            context.success();
+        } else {
+            throw new AuthenticationFlowException("Unknown action: " + action,
+                    AuthenticationFlowError.INTERNAL_ERROR);
+        }
+    }
+
+    @Override
+    public boolean requiresUser() {
+        return false;
+    }
+
+    @Override
+    public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
+        return false;
+    }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpConfirmLinkAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpConfirmLinkAuthenticatorFactory.java
new file mode 100644
index 0000000..930a8bf
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpConfirmLinkAuthenticatorFactory.java
@@ -0,0 +1,86 @@
+package org.keycloak.authentication.authenticators.broker;
+
+import java.util.List;
+
+import org.keycloak.Config;
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.provider.ProviderConfigProperty;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class IdpConfirmLinkAuthenticatorFactory implements AuthenticatorFactory {
+
+    public static final String PROVIDER_ID = "idp-confirm-link";
+    static IdpConfirmLinkAuthenticator SINGLETON = new IdpConfirmLinkAuthenticator();
+
+    @Override
+    public Authenticator create(KeycloakSession session) {
+        return SINGLETON;
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+
+    }
+
+    @Override
+    public void close() {
+
+    }
+
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+
+    @Override
+    public String getReferenceCategory() {
+        return "confirmLink";
+    }
+
+    @Override
+    public boolean isConfigurable() {
+        return false;
+    }
+
+    public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
+            AuthenticationExecutionModel.Requirement.REQUIRED,
+            AuthenticationExecutionModel.Requirement.DISABLED};
+
+    @Override
+    public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
+        return REQUIREMENT_CHOICES;
+    }
+
+    @Override
+    public String getDisplayType() {
+        return "Confirm link existing account";
+    }
+
+    @Override
+    public String getHelpText() {
+        return "Show the form where user confirms if he wants to link identity provider with existing account or rather edit user profile data retrieved from identity provider to avoid conflict";
+    }
+
+    @Override
+    public List<ProviderConfigProperty> getConfigProperties() {
+        return null;
+    }
+
+    @Override
+    public boolean isUserSetupAllowed() {
+        return false;
+    }
+
+
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java
new file mode 100644
index 0000000..b4ee957
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java
@@ -0,0 +1,123 @@
+package org.keycloak.authentication.authenticators.broker;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.core.Response;
+
+import org.jboss.logging.Logger;
+import org.keycloak.authentication.AuthenticationFlowContext;
+import org.keycloak.authentication.authenticators.broker.util.ExistingUserInfo;
+import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
+import org.keycloak.broker.provider.BrokeredIdentityContext;
+import org.keycloak.events.Details;
+import org.keycloak.events.Errors;
+import org.keycloak.models.AuthenticatorConfigModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.services.messages.Messages;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class IdpCreateUserIfUniqueAuthenticator extends AbstractIdpAuthenticator {
+
+    protected static Logger logger = Logger.getLogger(IdpCreateUserIfUniqueAuthenticator.class);
+
+
+    @Override
+    protected void actionImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) {
+    }
+
+    @Override
+    protected void authenticateImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) {
+
+        KeycloakSession session = context.getSession();
+        RealmModel realm = context.getRealm();
+
+        if (context.getClientSession().getNote(EXISTING_USER_INFO) != null) {
+            context.attempted();
+            return;
+        }
+
+        ExistingUserInfo duplication = checkExistingUser(context, serializedCtx, brokerContext);
+
+        if (duplication == null) {
+            logger.debugf("No duplication detected. Creating account for user '%s' and linking with identity provider '%s' .",
+                    brokerContext.getModelUsername(), brokerContext.getIdpConfig().getAlias());
+
+            UserModel federatedUser = session.users().addUser(realm, brokerContext.getModelUsername());
+            federatedUser.setEnabled(true);
+            federatedUser.setEmail(brokerContext.getEmail());
+            federatedUser.setFirstName(brokerContext.getFirstName());
+            federatedUser.setLastName(brokerContext.getLastName());
+
+            for (Map.Entry<String, List<String>> attr : serializedCtx.getAttributes().entrySet()) {
+                federatedUser.setAttribute(attr.getKey(), attr.getValue());
+            }
+
+            AuthenticatorConfigModel config = context.getAuthenticatorConfig();
+            if (config != null && Boolean.parseBoolean(config.getConfig().get(IdpCreateUserIfUniqueAuthenticatorFactory.REQUIRE_PASSWORD_UPDATE_AFTER_REGISTRATION))) {
+                logger.debugf("User '%s' required to update password", federatedUser.getUsername());
+                federatedUser.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
+            }
+
+            // TODO: Event
+
+            context.setUser(federatedUser);
+            context.getClientSession().setNote(BROKER_REGISTERED_NEW_USER, "true");
+            context.success();
+        } else {
+            logger.debugf("Duplication detected. There is already existing user with %s '%s' .",
+                    duplication.getDuplicateAttributeName(), duplication.getDuplicateAttributeValue());
+
+            // Set duplicated user, so next authenticators can deal with it
+            context.getClientSession().setNote(EXISTING_USER_INFO, duplication.serialize());
+
+            Response challengeResponse = context.form()
+                    .setError(Messages.FEDERATED_IDENTITY_EXISTS, duplication.getDuplicateAttributeName(), duplication.getDuplicateAttributeValue())
+                    .createErrorPage();
+            context.challenge(challengeResponse);
+
+            if (context.getExecution().isRequired()) {
+                context.getEvent()
+                        .user(duplication.getExistingUserId())
+                        .detail("existing_" + duplication.getDuplicateAttributeName(), duplication.getDuplicateAttributeValue())
+                        .removeDetail(Details.AUTH_METHOD)
+                        .removeDetail(Details.AUTH_TYPE)
+                        .error(Errors.FEDERATED_IDENTITY_EXISTS);
+            }
+        }
+    }
+
+    // Could be overriden to detect duplication based on other criterias (firstName, lastName, ...)
+    protected ExistingUserInfo checkExistingUser(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) {
+
+        if (brokerContext.getEmail() != null) {
+            UserModel existingUser = context.getSession().users().getUserByEmail(brokerContext.getEmail(), context.getRealm());
+            if (existingUser != null) {
+                return new ExistingUserInfo(existingUser.getId(), UserModel.EMAIL, existingUser.getEmail());
+            }
+        }
+
+        UserModel existingUser = context.getSession().users().getUserByUsername(brokerContext.getModelUsername(), context.getRealm());
+        if (existingUser != null) {
+            return new ExistingUserInfo(existingUser.getId(), UserModel.USERNAME, existingUser.getUsername());
+        }
+
+        return null;
+    }
+
+
+    @Override
+    public boolean requiresUser() {
+        return false;
+    }
+
+    @Override
+    public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
+        return true;
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticatorFactory.java
new file mode 100644
index 0000000..468e8cc
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticatorFactory.java
@@ -0,0 +1,101 @@
+package org.keycloak.authentication.authenticators.broker;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.keycloak.Config;
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.provider.ProviderConfigProperty;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class IdpCreateUserIfUniqueAuthenticatorFactory implements AuthenticatorFactory {
+
+    public static final String PROVIDER_ID = "idp-create-user-if-unique";
+    static IdpCreateUserIfUniqueAuthenticator SINGLETON = new IdpCreateUserIfUniqueAuthenticator();
+
+    public static final String REQUIRE_PASSWORD_UPDATE_AFTER_REGISTRATION = "require.password.update.after.registration";
+
+    @Override
+    public Authenticator create(KeycloakSession session) {
+        return SINGLETON;
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+
+    }
+
+    @Override
+    public void close() {
+
+    }
+
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+
+    @Override
+    public String getReferenceCategory() {
+        return "createUserIfUnique";
+    }
+
+    @Override
+    public boolean isConfigurable() {
+        return true;
+    }
+
+    public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
+            AuthenticationExecutionModel.Requirement.ALTERNATIVE,
+            AuthenticationExecutionModel.Requirement.REQUIRED,
+            AuthenticationExecutionModel.Requirement.DISABLED};
+
+    @Override
+    public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
+        return REQUIREMENT_CHOICES;
+    }
+
+    @Override
+    public String getDisplayType() {
+        return "Create User If Unique";
+    }
+
+    @Override
+    public String getHelpText() {
+        return "Detect if there is existing Keycloak account with same email like identity provider. If no, create new user";
+    }
+
+    @Override
+    public boolean isUserSetupAllowed() {
+        return false;
+    }
+
+    private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
+
+    static {
+        ProviderConfigProperty property;
+        property = new ProviderConfigProperty();
+        property.setName(REQUIRE_PASSWORD_UPDATE_AFTER_REGISTRATION);
+        property.setLabel("Require Password Update After Registration");
+        property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
+        property.setHelpText("If this option is true and new user is successfully imported from Identity Provider to Keycloak (there is no duplicated email or username detected in Keycloak DB), then this user is required to update his password");
+        configProperties.add(property);
+    }
+
+
+    @Override
+    public List<ProviderConfigProperty> getConfigProperties() {
+        return configProperties;
+    }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java
new file mode 100644
index 0000000..ae28d3e
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java
@@ -0,0 +1,144 @@
+package org.keycloak.authentication.authenticators.broker;
+
+import java.util.concurrent.TimeUnit;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+
+import org.jboss.logging.Logger;
+import org.keycloak.authentication.AuthenticationFlowContext;
+import org.keycloak.authentication.AuthenticationFlowError;
+import org.keycloak.authentication.requiredactions.VerifyEmail;
+import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
+import org.keycloak.broker.provider.BrokeredIdentityContext;
+import org.keycloak.email.EmailException;
+import org.keycloak.email.EmailProvider;
+import org.keycloak.events.Details;
+import org.keycloak.events.Errors;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.events.EventType;
+import org.keycloak.login.LoginFormsProvider;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.Constants;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.services.messages.Messages;
+import org.keycloak.services.resources.LoginActionsService;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class IdpEmailVerificationAuthenticator extends AbstractIdpAuthenticator {
+
+    protected static Logger logger = Logger.getLogger(IdpEmailVerificationAuthenticator.class);
+
+    @Override
+    protected void authenticateImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) {
+        KeycloakSession session = context.getSession();
+        RealmModel realm = context.getRealm();
+        ClientSessionModel clientSession = context.getClientSession();
+
+        if (realm.getSmtpConfig().size() == 0) {
+            logger.warnf("Smtp is not configured for the realm. Ignoring email verification authenticator");
+            context.attempted();
+            return;
+        }
+
+        // Create action cookie to detect if email verification happened in same browser
+        LoginActionsService.createActionCookie(context.getRealm(), context.getUriInfo(), context.getConnection(), context.getClientSession().getId());
+
+        VerifyEmail.setupKey(clientSession);
+
+        UserModel existingUser = getExistingUser(session, realm, clientSession);
+
+        String link = UriBuilder.fromUri(context.getActionUrl())
+                .queryParam(Constants.KEY, clientSession.getNote(Constants.VERIFY_EMAIL_KEY))
+                .build().toString();
+
+        EventBuilder event = context.getEvent().clone().event(EventType.SEND_IDENTITY_PROVIDER_LINK)
+                .user(existingUser)
+                .detail(Details.USERNAME, existingUser.getUsername())
+                .detail(Details.EMAIL, existingUser.getEmail())
+                .detail(Details.CODE_ID, clientSession.getId())
+                .removeDetail(Details.AUTH_METHOD)
+                .removeDetail(Details.AUTH_TYPE);
+
+        long expiration = TimeUnit.SECONDS.toMinutes(context.getRealm().getAccessCodeLifespanUserAction());
+        try {
+
+            context.getSession().getProvider(EmailProvider.class)
+                    .setRealm(realm)
+                    .setUser(existingUser)
+                    .setAttribute(EmailProvider.IDENTITY_PROVIDER_BROKER_CONTEXT, brokerContext)
+                    .sendConfirmIdentityBrokerLink(link, expiration);
+
+            event.success();
+        } catch (EmailException e) {
+            event.error(Errors.EMAIL_SEND_FAILED);
+
+            logger.error("Failed to send email to confirm identity broker linking", e);
+            Response challenge = context.form()
+                    .setError(Messages.EMAIL_SENT_ERROR)
+                    .createErrorPage();
+            context.failure(AuthenticationFlowError.INTERNAL_ERROR, challenge);
+            return;
+        }
+
+        Response challenge = context.form()
+                .setStatus(Response.Status.OK)
+                .setAttribute(LoginFormsProvider.IDENTITY_PROVIDER_BROKER_CONTEXT, brokerContext)
+                .createIdpLinkEmailPage();
+        context.forceChallenge(challenge);
+    }
+
+    @Override
+    protected void actionImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) {
+        MultivaluedMap<String, String> queryParams = context.getSession().getContext().getUri().getQueryParameters();
+        String key = queryParams.getFirst(Constants.KEY);
+        ClientSessionModel clientSession = context.getClientSession();
+        RealmModel realm = context.getRealm();
+        KeycloakSession session = context.getSession();
+
+        if (key != null) {
+            String keyFromSession = clientSession.getNote(Constants.VERIFY_EMAIL_KEY);
+            clientSession.removeNote(Constants.VERIFY_EMAIL_KEY);
+            if (key.equals(keyFromSession)) {
+                UserModel existingUser = getExistingUser(session, realm, clientSession);
+
+                logger.debugf("User '%s' confirmed that wants to link with identity provider '%s' . Identity provider username is '%s' ", existingUser.getUsername(),
+                        brokerContext.getIdpConfig().getAlias(), brokerContext.getUsername());
+
+                String actionCookieValue = LoginActionsService.getActionCookie(session.getContext().getRequestHeaders(), realm, session.getContext().getUri(), context.getConnection());
+                if (actionCookieValue == null || !actionCookieValue.equals(clientSession.getId())) {
+                    clientSession.setNote(IS_DIFFERENT_BROWSER, "true");
+                }
+
+                context.setUser(existingUser);
+                context.success();
+            } else {
+                logger.error("Key parameter don't match with the expected value from client session");
+                Response challengeResponse = context.form()
+                        .setError(Messages.INVALID_ACCESS_CODE)
+                        .createErrorPage();
+                context.failureChallenge(AuthenticationFlowError.IDENTITY_PROVIDER_ERROR, challengeResponse);
+            }
+        } else {
+            Response challengeResponse = context.form()
+                    .setError(Messages.MISSING_PARAMETER, Constants.KEY)
+                    .createErrorPage();
+            context.failureChallenge(AuthenticationFlowError.IDENTITY_PROVIDER_ERROR, challengeResponse);
+        }
+    }
+
+    @Override
+    public boolean requiresUser() {
+        return false;
+    }
+
+    @Override
+    public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
+        return false;
+    }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticatorFactory.java
new file mode 100644
index 0000000..e431966
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticatorFactory.java
@@ -0,0 +1,85 @@
+package org.keycloak.authentication.authenticators.broker;
+
+import java.util.List;
+
+import org.keycloak.Config;
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.provider.ProviderConfigProperty;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class IdpEmailVerificationAuthenticatorFactory implements AuthenticatorFactory {
+
+    public static final String PROVIDER_ID = "idp-email-verification";
+    static IdpEmailVerificationAuthenticator SINGLETON = new IdpEmailVerificationAuthenticator();
+
+    @Override
+    public Authenticator create(KeycloakSession session) {
+        return SINGLETON;
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+
+    }
+
+    @Override
+    public void close() {
+
+    }
+
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+
+    @Override
+    public String getReferenceCategory() {
+        return "emailVerification";
+    }
+
+    @Override
+    public boolean isConfigurable() {
+        return false;
+    }
+
+    public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
+            AuthenticationExecutionModel.Requirement.ALTERNATIVE,
+            AuthenticationExecutionModel.Requirement.REQUIRED,
+            AuthenticationExecutionModel.Requirement.DISABLED};
+
+    @Override
+    public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
+        return REQUIREMENT_CHOICES;
+    }
+
+    @Override
+    public String getDisplayType() {
+        return "Verify existing account by Email";
+    }
+
+    @Override
+    public String getHelpText() {
+        return "Email verification of existing Keycloak user, that wants to link his user account with identity provider";
+    }
+
+    @Override
+    public List<ProviderConfigProperty> getConfigProperties() {
+        return null;
+    }
+
+    @Override
+    public boolean isUserSetupAllowed() {
+        return false;
+    }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpReviewProfileAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpReviewProfileAuthenticator.java
new file mode 100644
index 0000000..04eb77b
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpReviewProfileAuthenticator.java
@@ -0,0 +1,126 @@
+package org.keycloak.authentication.authenticators.broker;
+
+import java.util.List;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+
+import org.jboss.logging.Logger;
+import org.keycloak.authentication.AuthenticationFlowContext;
+import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
+import org.keycloak.broker.provider.BrokeredIdentityContext;
+import org.keycloak.common.util.ObjectUtil;
+import org.keycloak.events.Details;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.events.EventType;
+import org.keycloak.login.LoginFormsProvider;
+import org.keycloak.models.AuthenticatorConfigModel;
+import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.FormMessage;
+import org.keycloak.representations.idm.IdentityProviderRepresentation;
+import org.keycloak.services.resources.AttributeFormDataProcessor;
+import org.keycloak.services.validation.Validation;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class IdpReviewProfileAuthenticator extends AbstractIdpAuthenticator {
+
+    protected static Logger logger = Logger.getLogger(IdpReviewProfileAuthenticator.class);
+
+    @Override
+    public boolean requiresUser() {
+        return false;
+    }
+
+    @Override
+    protected void authenticateImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext userCtx, BrokeredIdentityContext brokerContext) {
+        IdentityProviderModel idpConfig = brokerContext.getIdpConfig();
+
+        if (requiresUpdateProfilePage(context, userCtx, brokerContext)) {
+
+            logger.debugf("Identity provider '%s' requires update profile action for broker user '%s'.", idpConfig.getAlias(), userCtx.getUsername());
+
+            // No formData for first render. The profile is rendered from userCtx
+            Response challengeResponse = context.form()
+                    .setAttribute(LoginFormsProvider.UPDATE_PROFILE_CONTEXT_ATTR, userCtx)
+                    .setFormData(null)
+                    .createUpdateProfilePage();
+            context.challenge(challengeResponse);
+        } else {
+            // Not required to update profile. Marked success
+            context.success();
+        }
+    }
+
+    protected boolean requiresUpdateProfilePage(AuthenticationFlowContext context, SerializedBrokeredIdentityContext userCtx, BrokeredIdentityContext brokerContext) {
+        String enforceUpdateProfile = context.getClientSession().getNote(ENFORCE_UPDATE_PROFILE);
+        if (Boolean.parseBoolean(enforceUpdateProfile)) {
+            return true;
+        }
+
+        String updateProfileFirstLogin;
+        AuthenticatorConfigModel authenticatorConfig = context.getAuthenticatorConfig();
+        if (authenticatorConfig == null || !authenticatorConfig.getConfig().containsKey(IdpReviewProfileAuthenticatorFactory.UPDATE_PROFILE_ON_FIRST_LOGIN)) {
+            updateProfileFirstLogin = IdentityProviderRepresentation.UPFLM_MISSING;
+        } else {
+            updateProfileFirstLogin = authenticatorConfig.getConfig().get(IdpReviewProfileAuthenticatorFactory.UPDATE_PROFILE_ON_FIRST_LOGIN);
+        }
+
+        RealmModel realm = context.getRealm();
+        return IdentityProviderRepresentation.UPFLM_ON.equals(updateProfileFirstLogin)
+                || (IdentityProviderRepresentation.UPFLM_MISSING.equals(updateProfileFirstLogin) && !Validation.validateUserMandatoryFields(realm, userCtx));
+    }
+
+    @Override
+    protected void actionImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext userCtx, BrokeredIdentityContext brokerContext) {
+        EventBuilder event = context.getEvent();
+        event.event(EventType.UPDATE_PROFILE);
+        MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
+
+        RealmModel realm = context.getRealm();
+
+        List<FormMessage> errors = Validation.validateUpdateProfileForm(true, formData);
+        if (errors != null && !errors.isEmpty()) {
+            Response challenge = context.form()
+                    .setErrors(errors)
+                    .setAttribute(LoginFormsProvider.UPDATE_PROFILE_CONTEXT_ATTR, userCtx)
+                    .setFormData(formData)
+                    .createUpdateProfilePage();
+            context.challenge(challenge);
+            return;
+        }
+
+        userCtx.setUsername(formData.getFirst(UserModel.USERNAME));
+        userCtx.setFirstName(formData.getFirst(UserModel.FIRST_NAME));
+        userCtx.setLastName(formData.getFirst(UserModel.LAST_NAME));
+
+        String email = formData.getFirst(UserModel.EMAIL);
+        if (!ObjectUtil.isEqualOrBothNull(email, userCtx.getEmail())) {
+            if (logger.isTraceEnabled()) {
+                logger.tracef("Email updated on updateProfile page to '%s' ", email);
+            }
+
+            userCtx.setEmail(email);
+            context.getClientSession().setNote(UPDATE_PROFILE_EMAIL_CHANGED, "true");
+        }
+
+        AttributeFormDataProcessor.process(formData, realm, userCtx);
+
+        userCtx.saveToClientSession(context.getClientSession());
+
+        logger.debugf("Profile updated successfully after first authentication with identity provider '%s' for broker user '%s'.", brokerContext.getIdpConfig().getAlias(), userCtx.getUsername());
+
+        event.detail(Details.UPDATED_EMAIL, email);
+        context.success();
+    }
+
+    @Override
+    public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
+        return true;
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpReviewProfileAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpReviewProfileAuthenticatorFactory.java
new file mode 100644
index 0000000..e10c924
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpReviewProfileAuthenticatorFactory.java
@@ -0,0 +1,107 @@
+package org.keycloak.authentication.authenticators.broker;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.keycloak.Config;
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.representations.idm.IdentityProviderRepresentation;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class IdpReviewProfileAuthenticatorFactory implements AuthenticatorFactory {
+
+    public static final String PROVIDER_ID = "idp-review-profile";
+    static IdpReviewProfileAuthenticator SINGLETON = new IdpReviewProfileAuthenticator();
+
+    public static final String UPDATE_PROFILE_ON_FIRST_LOGIN = "update.profile.on.first.login";
+
+    @Override
+    public Authenticator create(KeycloakSession session) {
+        return SINGLETON;
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+
+    }
+
+    @Override
+    public void close() {
+
+    }
+
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+
+    @Override
+    public String getReferenceCategory() {
+        return "reviewProfile";
+    }
+
+    @Override
+    public boolean isConfigurable() {
+        return true;
+    }
+
+    public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
+            AuthenticationExecutionModel.Requirement.REQUIRED,
+            AuthenticationExecutionModel.Requirement.DISABLED};
+
+    @Override
+    public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
+        return REQUIREMENT_CHOICES;
+    }
+
+    @Override
+    public String getDisplayType() {
+        return "Review Profile";
+    }
+
+    @Override
+    public String getHelpText() {
+        return "User reviews and updates profile data retrieved from Identity Provider in the displayed form";
+    }
+
+    @Override
+    public boolean isUserSetupAllowed() {
+        return false;
+    }
+
+    private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
+
+    static {
+        ProviderConfigProperty property;
+        property = new ProviderConfigProperty();
+        property.setName(UPDATE_PROFILE_ON_FIRST_LOGIN);
+        property.setLabel("{{:: 'update-profile-on-first-login' | translate}}");
+        property.setType(ProviderConfigProperty.LIST_TYPE);
+        List<String> updateProfileValues = Arrays.asList(IdentityProviderRepresentation.UPFLM_ON, IdentityProviderRepresentation.UPFLM_MISSING, IdentityProviderRepresentation.UPFLM_OFF);
+        property.setDefaultValue(updateProfileValues);
+        property.setHelpText("Define conditions under which a user has to review and update his profile after first-time login. Value 'On' means that"
+                + " page for reviewing profile will be displayed and user can review and update his profile. Value 'off' means that page won't be displayed."
+                + " Value 'missing' means that page is displayed just when some required attribute is missing (wasn't downloaded from identity provider). Value 'missing' is the default one");
+
+        configProperties.add(property);
+    }
+
+
+    @Override
+    public List<ProviderConfigProperty> getConfigProperties() {
+        return configProperties;
+    }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUsernamePasswordForm.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUsernamePasswordForm.java
new file mode 100644
index 0000000..5e732d8
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUsernamePasswordForm.java
@@ -0,0 +1,55 @@
+package org.keycloak.authentication.authenticators.broker;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+
+import org.keycloak.authentication.AuthenticationFlowContext;
+import org.keycloak.authentication.AuthenticationFlowError;
+import org.keycloak.authentication.AuthenticationFlowException;
+import org.keycloak.authentication.authenticators.browser.UsernamePasswordForm;
+import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
+import org.keycloak.login.LoginFormsProvider;
+import org.keycloak.models.UserModel;
+import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.messages.Messages;
+
+/**
+ * Same like classic username+password form, but username is "known" and user can't change it
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class IdpUsernamePasswordForm extends UsernamePasswordForm {
+
+    @Override
+    protected Response challenge(AuthenticationFlowContext context, MultivaluedMap<String, String> formData) {
+        UserModel existingUser = AbstractIdpAuthenticator.getExistingUser(context.getSession(), context.getRealm(), context.getClientSession());
+
+        return setupForm(context, formData, existingUser)
+                .setStatus(Response.Status.OK)
+                .createLogin();
+    }
+
+    @Override
+    protected boolean validateForm(AuthenticationFlowContext context, MultivaluedMap<String, String> formData) {
+        UserModel existingUser = AbstractIdpAuthenticator.getExistingUser(context.getSession(), context.getRealm(), context.getClientSession());
+        context.setUser(existingUser);
+
+        // Restore formData for the case of error
+        setupForm(context, formData, existingUser);
+
+        return validatePassword(context, existingUser, formData);
+    }
+
+    protected LoginFormsProvider setupForm(AuthenticationFlowContext context, MultivaluedMap<String, String> formData, UserModel existingUser) {
+        SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromClientSession(context.getClientSession());
+        if (serializedCtx == null) {
+            throw new AuthenticationFlowException("Not found serialized context in clientSession", AuthenticationFlowError.IDENTITY_PROVIDER_ERROR);
+        }
+
+        formData.add(AuthenticationManager.FORM_USERNAME, existingUser.getUsername());
+        return context.form()
+                .setFormData(formData)
+                .setAttribute(LoginFormsProvider.USERNAME_EDIT_DISABLED, true)
+                .setSuccess(Messages.FEDERATED_IDENTITY_CONFIRM_REAUTHENTICATE_MESSAGE, existingUser.getUsername(), serializedCtx.getIdentityProviderId());
+    }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUsernamePasswordFormFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUsernamePasswordFormFactory.java
new file mode 100644
index 0000000..2adaead
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUsernamePasswordFormFactory.java
@@ -0,0 +1,35 @@
+package org.keycloak.authentication.authenticators.broker;
+
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.authentication.authenticators.browser.UsernamePasswordForm;
+import org.keycloak.authentication.authenticators.browser.UsernamePasswordFormFactory;
+import org.keycloak.models.KeycloakSession;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class IdpUsernamePasswordFormFactory extends UsernamePasswordFormFactory {
+
+    public static final String PROVIDER_ID = "idp-username-password-form";
+    public static final UsernamePasswordForm IDP_SINGLETON = new IdpUsernamePasswordForm();
+
+    @Override
+    public Authenticator create(KeycloakSession session) {
+        return IDP_SINGLETON;
+    }
+
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+
+    @Override
+    public String getHelpText() {
+        return "Validates a password from login form. Username is already known from identity provider authentication";
+    }
+
+    @Override
+    public String getDisplayType() {
+        return "Username Password Form for identity provider reauthentication";
+    }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/util/ExistingUserInfo.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/util/ExistingUserInfo.java
new file mode 100644
index 0000000..2d6ea7f
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/util/ExistingUserInfo.java
@@ -0,0 +1,62 @@
+package org.keycloak.authentication.authenticators.broker.util;
+
+import java.io.IOException;
+
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ExistingUserInfo {
+    private String existingUserId;
+    private String duplicateAttributeName;
+    private String duplicateAttributeValue;
+
+    public ExistingUserInfo() {}
+
+    public ExistingUserInfo(String existingUserId, String duplicateAttributeName, String duplicateAttributeValue) {
+        this.existingUserId = existingUserId;
+        this.duplicateAttributeName = duplicateAttributeName;
+        this.duplicateAttributeValue = duplicateAttributeValue;
+    }
+
+    public String getExistingUserId() {
+        return existingUserId;
+    }
+
+    public void setExistingUserId(String existingUserId) {
+        this.existingUserId = existingUserId;
+    }
+
+    public String getDuplicateAttributeName() {
+        return duplicateAttributeName;
+    }
+
+    public void setDuplicateAttributeName(String duplicateAttributeName) {
+        this.duplicateAttributeName = duplicateAttributeName;
+    }
+
+    public String getDuplicateAttributeValue() {
+        return duplicateAttributeValue;
+    }
+
+    public void setDuplicateAttributeValue(String duplicateAttributeValue) {
+        this.duplicateAttributeValue = duplicateAttributeValue;
+    }
+
+    public String serialize() {
+        try {
+            return JsonSerialization.writeValueAsString(this);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static ExistingUserInfo deserialize(String serialized) {
+        try {
+            return JsonSerialization.readValue(serialized, ExistingUserInfo.class);
+        } catch (IOException ioe) {
+            throw new RuntimeException(ioe);
+        }
+    }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/util/SerializedBrokeredIdentityContext.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/util/SerializedBrokeredIdentityContext.java
new file mode 100644
index 0000000..8f1b026
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/util/SerializedBrokeredIdentityContext.java
@@ -0,0 +1,327 @@
+package org.keycloak.authentication.authenticators.broker.util;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator;
+import org.keycloak.authentication.requiredactions.util.UpdateProfileContext;
+import org.keycloak.broker.provider.BrokeredIdentityContext;
+import org.keycloak.broker.provider.IdentityProvider;
+import org.keycloak.broker.provider.IdentityProviderDataMarshaller;
+import org.keycloak.common.util.Base64Url;
+import org.keycloak.common.util.reflections.Reflections;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.RealmModel;
+import org.keycloak.services.resources.IdentityBrokerService;
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class SerializedBrokeredIdentityContext implements UpdateProfileContext {
+
+    private String id;
+    private String brokerUsername;
+    private String modelUsername;
+    private String email;
+    private String firstName;
+    private String lastName;
+    private String brokerSessionId;
+    private String brokerUserId;
+    private String code;
+    private String token;
+
+    private String identityProviderId;
+    private Map<String, ContextDataEntry> contextData = new HashMap<>();
+
+    @JsonIgnore
+    @Override
+    public boolean isEditUsernameAllowed() {
+        return true;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    @JsonIgnore
+    @Override
+    public String getUsername() {
+        return modelUsername;
+    }
+
+    @Override
+    public void setUsername(String username) {
+        this.modelUsername = username;
+    }
+
+    public String getModelUsername() {
+        return modelUsername;
+    }
+
+    public void setModelUsername(String modelUsername) {
+        this.modelUsername = modelUsername;
+    }
+
+    public String getBrokerUsername() {
+        return brokerUsername;
+    }
+
+    public void setBrokerUsername(String modelUsername) {
+        this.brokerUsername = modelUsername;
+    }
+
+    @Override
+    public String getEmail() {
+        return email;
+    }
+
+    @Override
+    public void setEmail(String email) {
+        this.email = email;
+    }
+
+    @Override
+    public String getFirstName() {
+        return firstName;
+    }
+
+    @Override
+    public void setFirstName(String firstName) {
+        this.firstName = firstName;
+    }
+
+    @Override
+    public String getLastName() {
+        return lastName;
+    }
+
+    @Override
+    public void setLastName(String lastName) {
+        this.lastName = lastName;
+    }
+
+    public String getBrokerSessionId() {
+        return brokerSessionId;
+    }
+
+    public void setBrokerSessionId(String brokerSessionId) {
+        this.brokerSessionId = brokerSessionId;
+    }
+
+    public String getBrokerUserId() {
+        return brokerUserId;
+    }
+
+    public void setBrokerUserId(String brokerUserId) {
+        this.brokerUserId = brokerUserId;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+
+    public String getToken() {
+        return token;
+    }
+
+    public void setToken(String token) {
+        this.token = token;
+    }
+
+    public String getIdentityProviderId() {
+        return identityProviderId;
+    }
+
+    public void setIdentityProviderId(String identityProviderId) {
+        this.identityProviderId = identityProviderId;
+    }
+
+    public Map<String, ContextDataEntry> getContextData() {
+        return contextData;
+    }
+
+    public void setContextData(Map<String, ContextDataEntry> contextData) {
+        this.contextData = contextData;
+    }
+
+    @Override
+    public Map<String, List<String>> getAttributes() {
+        Map<String, List<String>> result = new HashMap<>();
+
+        for (Map.Entry<String, ContextDataEntry> entry : this.contextData.entrySet()) {
+            if (entry.getKey().startsWith("user.attributes.")) {
+                ContextDataEntry ctxEntry = entry.getValue();
+                String asString = ctxEntry.getData();
+                try {
+                    List<String> asList = JsonSerialization.readValue(asString, List.class);
+                    result.put(entry.getKey().substring(16), asList);
+                } catch (IOException ioe) {
+                    throw new RuntimeException(ioe);
+                }
+            }
+        }
+
+        return result;
+    }
+
+    @Override
+    public void setAttribute(String key, List<String> value) {
+        try {
+            String listStr = JsonSerialization.writeValueAsString(value);
+            ContextDataEntry ctxEntry = ContextDataEntry.create(List.class.getName(), listStr);
+            this.contextData.put("user.attributes." + key, ctxEntry);
+        } catch (IOException ioe) {
+            throw new RuntimeException(ioe);
+        }
+    }
+
+    @Override
+    public List<String> getAttribute(String key) {
+        ContextDataEntry ctxEntry = this.contextData.get("user.attributes." + key);
+        if (ctxEntry != null) {
+            try {
+                String asString = ctxEntry.getData();
+                List<String> asList = JsonSerialization.readValue(asString, List.class);
+                return asList;
+            } catch (IOException ioe) {
+                throw new RuntimeException(ioe);
+            }
+        } else {
+            return null;
+        }
+    }
+
+    public BrokeredIdentityContext deserialize(KeycloakSession session, ClientSessionModel clientSession) {
+        BrokeredIdentityContext ctx = new BrokeredIdentityContext(getId());
+
+        ctx.setUsername(getBrokerUsername());
+        ctx.setModelUsername(getModelUsername());
+        ctx.setEmail(getEmail());
+        ctx.setFirstName(getFirstName());
+        ctx.setLastName(getLastName());
+        ctx.setBrokerSessionId(getBrokerSessionId());
+        ctx.setBrokerUserId(getBrokerUserId());
+        ctx.setCode(getCode());
+        ctx.setToken(getToken());
+
+        RealmModel realm = clientSession.getRealm();
+        IdentityProviderModel idpConfig = realm.getIdentityProviderByAlias(getIdentityProviderId());
+        if (idpConfig == null) {
+            throw new ModelException("Can't find identity provider with ID " + getIdentityProviderId() + " in realm " + realm.getName());
+        }
+        IdentityProvider idp = IdentityBrokerService.getIdentityProvider(session, realm, idpConfig.getAlias());
+        ctx.setIdpConfig(idpConfig);
+        ctx.setIdp(idp);
+
+        IdentityProviderDataMarshaller serializer = idp.getMarshaller();
+
+        for (Map.Entry<String, ContextDataEntry> entry : getContextData().entrySet()) {
+            try {
+                ContextDataEntry value = entry.getValue();
+                Class<?> clazz = Reflections.classForName(value.getClazz(), this.getClass().getClassLoader());
+
+                Object deserialized = serializer.deserialize(value.getData(), clazz);
+
+                ctx.getContextData().put(entry.getKey(), deserialized);
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        ctx.setClientSession(clientSession);
+        return ctx;
+    }
+
+    public static SerializedBrokeredIdentityContext serialize(BrokeredIdentityContext context) {
+        SerializedBrokeredIdentityContext ctx = new SerializedBrokeredIdentityContext();
+        ctx.setId(context.getId());
+        ctx.setBrokerUsername(context.getUsername());
+        ctx.setModelUsername(context.getModelUsername());
+        ctx.setEmail(context.getEmail());
+        ctx.setFirstName(context.getFirstName());
+        ctx.setLastName(context.getLastName());
+        ctx.setBrokerSessionId(context.getBrokerSessionId());
+        ctx.setBrokerUserId(context.getBrokerUserId());
+        ctx.setCode(context.getCode());
+        ctx.setToken(context.getToken());
+        ctx.setIdentityProviderId(context.getIdpConfig().getAlias());
+
+        IdentityProviderDataMarshaller serializer = context.getIdp().getMarshaller();
+
+        for (Map.Entry<String, Object> entry : context.getContextData().entrySet()) {
+            Object value = entry.getValue();
+            String serializedValue = serializer.serialize(value);
+
+            ContextDataEntry ctxEntry = ContextDataEntry.create(value.getClass().getName(), serializedValue);
+            ctx.getContextData().put(entry.getKey(), ctxEntry);
+        }
+        return ctx;
+    }
+
+    // Save this context as note to clientSession
+    public void saveToClientSession(ClientSessionModel clientSession) {
+        try {
+            String asString = JsonSerialization.writeValueAsString(this);
+            clientSession.setNote(AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE, asString);
+        } catch (IOException ioe) {
+            throw new RuntimeException(ioe);
+        }
+    }
+
+    public static SerializedBrokeredIdentityContext readFromClientSession(ClientSessionModel clientSession) {
+        String asString = clientSession.getNote(AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE);
+        if (asString == null) {
+            return null;
+        } else {
+            try {
+                return JsonSerialization.readValue(asString, SerializedBrokeredIdentityContext.class);
+            } catch (IOException ioe) {
+                throw new RuntimeException(ioe);
+            }
+        }
+    }
+
+    public static class ContextDataEntry {
+
+        private String clazz;
+        private String data;
+
+        public String getClazz() {
+            return clazz;
+        }
+
+        public void setClazz(String clazz) {
+            this.clazz = clazz;
+        }
+
+        public String getData() {
+            return data;
+        }
+
+        public void setData(String data) {
+            this.data = data;
+        }
+
+        public static ContextDataEntry create(String clazz, String data) {
+            ContextDataEntry entry = new ContextDataEntry();
+            entry.setClazz(clazz);
+            entry.setData(data);
+            return entry;
+        }
+    }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java
index 294d220..5afaa3d 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java
@@ -71,12 +71,16 @@ public abstract class AbstractUsernameFormAuthenticator extends AbstractFormAuth
             context.failureChallenge(AuthenticationFlowError.INVALID_USER, challengeResponse);
             return true;
         }
+        return false;
+    }
+
+    public boolean enabledUser(AuthenticationFlowContext context, UserModel user) {
         if (!user.isEnabled()) {
             context.getEvent().user(user);
             context.getEvent().error(Errors.USER_DISABLED);
             Response challengeResponse = disabledUser(context);
             context.failureChallenge(AuthenticationFlowError.USER_DISABLED, challengeResponse);
-            return true;
+            return false;
         }
         if (context.getRealm().isBruteForceProtected()) {
             if (context.getProtector().isTemporarilyDisabled(context.getSession(), context.getRealm(), user.getUsername())) {
@@ -84,13 +88,13 @@ public abstract class AbstractUsernameFormAuthenticator extends AbstractFormAuth
                 context.getEvent().error(Errors.USER_TEMPORARILY_DISABLED);
                 Response challengeResponse = temporarilyDisabledUser(context);
                 context.failureChallenge(AuthenticationFlowError.USER_TEMPORARILY_DISABLED, challengeResponse);
-                return true;
+                return false;
             }
         }
-        return false;
+        return true;
     }
 
-    public boolean validateUser(AuthenticationFlowContext context, MultivaluedMap<String, String> inputData) {
+    public boolean validateUserAndPassword(AuthenticationFlowContext context, MultivaluedMap<String, String> inputData) {
         String username = inputData.getFirst(AuthenticationManager.FORM_USERNAME);
         if (username == null) {
             context.getEvent().error(Errors.USER_NOT_FOUND);
@@ -117,7 +121,18 @@ public abstract class AbstractUsernameFormAuthenticator extends AbstractFormAuth
             return false;
         }
 
-        if (invalidUser(context, user)) return false;
+        if (invalidUser(context, user)){
+            return false;
+        }
+
+        if (!validatePassword(context, user, inputData)){
+            return false;
+        }
+
+        if(!enabledUser(context, user)){
+            return false;
+        }
+
         String rememberMe = inputData.getFirst("rememberMe");
         boolean remember = rememberMe != null && rememberMe.equalsIgnoreCase("on");
         if (remember) {
@@ -130,23 +145,13 @@ public abstract class AbstractUsernameFormAuthenticator extends AbstractFormAuth
         return true;
     }
 
-    public boolean validatePassword(AuthenticationFlowContext context, MultivaluedMap<String, String> inputData) {
+    public boolean validatePassword(AuthenticationFlowContext context, UserModel user, MultivaluedMap<String, String> inputData) {
         List<UserCredentialModel> credentials = new LinkedList<>();
         String password = inputData.getFirst(CredentialRepresentation.PASSWORD);
-        if (password == null || password.isEmpty()) {
-            if (context.getUser() != null) {
-                context.getEvent().user(context.getUser());
-            }
-            context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
-            Response challengeResponse = invalidCredentials(context);
-            context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challengeResponse);
-            context.clearUser();
-            return false;
-        }
         credentials.add(UserCredentialModel.password(password));
-        boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials);
+        boolean valid = context.getSession().users().validCredentials(context.getRealm(), user, credentials);
         if (!valid) {
-            context.getEvent().user(context.getUser());
+            context.getEvent().user(user);
             context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
             Response challengeResponse = invalidCredentials(context);
             context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challengeResponse);
@@ -155,4 +160,5 @@ public abstract class AbstractUsernameFormAuthenticator extends AbstractFormAuth
         }
         return true;
     }
+
 }
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordForm.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordForm.java
index 70d9fd9..7463638 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordForm.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordForm.java
@@ -38,7 +38,7 @@ public class UsernamePasswordForm extends AbstractUsernameFormAuthenticator impl
     }
 
     protected boolean validateForm(AuthenticationFlowContext context, MultivaluedMap<String, String> formData) {
-        return validateUser(context, formData) && validatePassword(context, formData);
+        return validateUserAndPassword(context, formData);
     }
 
     @Override
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/ValidatePassword.java b/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/ValidatePassword.java
index cff7f37..21aa18d 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/ValidatePassword.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/ValidatePassword.java
@@ -31,15 +31,6 @@ public class ValidatePassword extends AbstractDirectGrantAuthenticator {
         MultivaluedMap<String, String> inputData = context.getHttpRequest().getDecodedFormParameters();
         List<UserCredentialModel> credentials = new LinkedList<>();
         String password = inputData.getFirst(CredentialRepresentation.PASSWORD);
-        if (password == null || password.isEmpty()) {
-            if (context.getUser() != null) {
-                context.getEvent().user(context.getUser());
-            }
-            context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
-            Response challengeResponse = errorResponse(Response.Status.UNAUTHORIZED.getStatusCode(), "invalid_grant", "Invalid user credentials");
-            context.failure(AuthenticationFlowError.INVALID_USER, challengeResponse);
-            return;
-        }
         credentials.add(UserCredentialModel.password(password));
         boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials);
         if (!valid) {
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialChooseUser.java b/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialChooseUser.java
index a624495..a3538aa 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialChooseUser.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialChooseUser.java
@@ -1,10 +1,12 @@
 package org.keycloak.authentication.authenticators.resetcred;
 
+import org.jboss.logging.Logger;
 import org.keycloak.Config;
 import org.keycloak.authentication.AuthenticationFlowContext;
 import org.keycloak.authentication.AuthenticationFlowError;
 import org.keycloak.authentication.Authenticator;
 import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator;
 import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator;
 import org.keycloak.email.EmailException;
 import org.keycloak.email.EmailProvider;
@@ -36,10 +38,22 @@ import java.util.concurrent.TimeUnit;
  */
 public class ResetCredentialChooseUser implements Authenticator, AuthenticatorFactory {
 
+    protected static Logger logger = Logger.getLogger(ResetCredentialChooseUser.class);
+
     public static final String PROVIDER_ID = "reset-credentials-choose-user";
 
     @Override
     public void authenticate(AuthenticationFlowContext context) {
+        String existingUserId = context.getClientSession().getNote(AbstractIdpAuthenticator.EXISTING_USER_INFO);
+        if (existingUserId != null) {
+            UserModel existingUser = AbstractIdpAuthenticator.getExistingUser(context.getSession(), context.getRealm(), context.getClientSession());
+
+            logger.debugf("Forget-password triggered when reauthenticating user after first broker login. Skipping reset-credential-choose-user screen and using user '%s' ", existingUser.getUsername());
+            context.setUser(existingUser);
+            context.success();
+            return;
+        }
+
         Response challenge = context.form().createPasswordReset();
         context.challenge(challenge);
     }
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java b/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java
index 8749360..678aac2 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java
@@ -14,6 +14,7 @@ import org.keycloak.events.Errors;
 import org.keycloak.events.EventBuilder;
 import org.keycloak.events.EventType;
 import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.Constants;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.RealmModel;
@@ -34,7 +35,7 @@ import java.util.concurrent.TimeUnit;
  */
 public class ResetCredentialEmail implements Authenticator, AuthenticatorFactory {
     public static final String RESET_CREDENTIAL_SECRET = "RESET_CREDENTIAL_SECRET";
-    public static final String KEY = "key";
+
     protected static Logger logger = Logger.getLogger(ResetCredentialEmail.class);
 
     public static final String PROVIDER_ID = "reset-credential-email";
@@ -67,7 +68,7 @@ public class ResetCredentialEmail implements Authenticator, AuthenticatorFactory
         // it can only be guessed once, and it must match watch is stored in the client session.
         String secret = HmacOTP.generateSecret(10);
         context.getClientSession().setNote(RESET_CREDENTIAL_SECRET, secret);
-        String link = UriBuilder.fromUri(context.getActionUrl()).queryParam(KEY, secret).build().toString();
+        String link = UriBuilder.fromUri(context.getActionUrl()).queryParam(Constants.KEY, secret).build().toString();
         long expiration = TimeUnit.SECONDS.toMinutes(context.getRealm().getAccessCodeLifespanUserAction());
         try {
 
@@ -93,7 +94,7 @@ public class ResetCredentialEmail implements Authenticator, AuthenticatorFactory
     @Override
     public void action(AuthenticationFlowContext context) {
         String secret = context.getClientSession().getNote(RESET_CREDENTIAL_SECRET);
-        String key = context.getUriInfo().getQueryParameters().getFirst(KEY);
+        String key = context.getUriInfo().getQueryParameters().getFirst(Constants.KEY);
 
         // Can only guess once!  We remove the note so another guess can't happen
         context.getClientSession().removeNote(RESET_CREDENTIAL_SECRET);
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateProfile.java b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateProfile.java
index 9630f3b..107f7cc 100755
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateProfile.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateProfile.java
@@ -52,7 +52,7 @@ public class UpdateProfile implements RequiredActionProvider, RequiredActionFact
         RealmModel realm = context.getRealm();
 
 
-        List<FormMessage> errors = Validation.validateUpdateProfileForm(realm, formData);
+        List<FormMessage> errors = Validation.validateUpdateProfileForm(realm.isEditUsernameAllowed(), formData);
         if (errors != null && !errors.isEmpty()) {
             Response challenge = context.form()
                     .setErrors(errors)
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/util/UpdateProfileContext.java b/services/src/main/java/org/keycloak/authentication/requiredactions/util/UpdateProfileContext.java
new file mode 100644
index 0000000..2acef37
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/util/UpdateProfileContext.java
@@ -0,0 +1,38 @@
+package org.keycloak.authentication.requiredactions.util;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Abstraction, which allows to display updateProfile page in various contexts (Required action of already existing user, or first identity provider
+ * login when user doesn't yet exists in Keycloak DB)
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface UpdateProfileContext {
+
+    boolean isEditUsernameAllowed();
+
+    String getUsername();
+
+    void setUsername(String username);
+
+    String getEmail();
+
+    void setEmail(String email);
+
+    String getFirstName();
+
+    void setFirstName(String firstName);
+
+    String getLastName();
+
+    void setLastName(String lastName);
+
+    Map<String, List<String>> getAttributes();
+
+    void setAttribute(String key, List<String> value);
+
+    List<String> getAttribute(String key);
+
+}
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/util/UserUpdateProfileContext.java b/services/src/main/java/org/keycloak/authentication/requiredactions/util/UserUpdateProfileContext.java
new file mode 100644
index 0000000..55d6dda
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/util/UserUpdateProfileContext.java
@@ -0,0 +1,81 @@
+package org.keycloak.authentication.requiredactions.util;
+
+import java.util.List;
+import java.util.Map;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UserUpdateProfileContext implements UpdateProfileContext {
+
+    private final RealmModel realm;
+    private final UserModel user;
+
+    public UserUpdateProfileContext(RealmModel realm, UserModel user) {
+        this.realm = realm;
+        this.user = user;
+    }
+
+    @Override
+    public boolean isEditUsernameAllowed() {
+        return realm.isEditUsernameAllowed();
+    }
+
+    @Override
+    public String getUsername() {
+        return user.getUsername();
+    }
+
+    @Override
+    public void setUsername(String username) {
+        user.setUsername(username);
+    }
+
+    @Override
+    public String getEmail() {
+        return user.getEmail();
+    }
+
+    @Override
+    public void setEmail(String email) {
+        user.setEmail(email);
+    }
+
+    @Override
+    public String getFirstName() {
+        return user.getFirstName();
+    }
+
+    @Override
+    public void setFirstName(String firstName) {
+        user.setFirstName(firstName);
+    }
+
+    @Override
+    public String getLastName() {
+        return user.getLastName();
+    }
+
+    @Override
+    public void setLastName(String lastName) {
+        user.setLastName(lastName);
+    }
+
+    @Override
+    public Map<String, List<String>> getAttributes() {
+        return user.getAttributes();
+    }
+
+    @Override
+    public void setAttribute(String key, List<String> value) {
+        user.setAttribute(key, value);
+    }
+
+    @Override
+    public List<String> getAttribute(String key) {
+        return user.getAttribute(key);
+    }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
index 8193140..e013857 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
@@ -347,6 +347,7 @@ public class AuthorizationEndpoint {
         processor.setClientSession(clientSession)
                 .setFlowPath(flowPath)
                 .setFlowId(flowId)
+                .setBrowserFlow(true)
                 .setConnection(clientConnection)
                 .setEventBuilder(event)
                 .setProtector(authManager.getProtector())
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
index 9b862ef..2250dd0 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
@@ -190,7 +190,7 @@ public class LogoutEndpoint {
     }
 
     private ClientModel authorizeClient() {
-        ClientModel client = AuthorizeClientUtil.authorizeClient(session, event, realm).getClient();
+        ClientModel client = AuthorizeClientUtil.authorizeClient(session, event).getClient();
 
         if (client.isBearerOnly()) {
             throw new ErrorResponseException("invalid_client", "Bearer-only not allowed", Response.Status.BAD_REQUEST);
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
index 1fb3e4a..34161d8 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
@@ -145,7 +145,7 @@ public class TokenEndpoint {
     }
 
     private void checkClient() {
-        AuthorizeClientUtil.ClientAuthResult clientAuth = AuthorizeClientUtil.authorizeClient(session, event, realm);
+        AuthorizeClientUtil.ClientAuthResult clientAuth = AuthorizeClientUtil.authorizeClient(session, event);
         client = clientAuth.getClient();
         clientAuthAttributes = clientAuth.getClientAuthAttributes();
 
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/GroupMembershipMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/GroupMembershipMapper.java
new file mode 100755
index 0000000..848a8e8
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/GroupMembershipMapper.java
@@ -0,0 +1,152 @@
+package org.keycloak.protocol.oidc.mappers;
+
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.protocol.ProtocolMapperUtils;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.IDToken;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Maps user group membership
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class GroupMembershipMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper {
+
+    private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
+
+    static {
+        ProviderConfigProperty property;
+        ProviderConfigProperty property1;
+        property1 = new ProviderConfigProperty();
+        property1.setName(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME);
+        property1.setLabel(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME_LABEL);
+        property1.setType(ProviderConfigProperty.STRING_TYPE);
+        property1.setDefaultValue("groups");
+        property1.setHelpText(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME_TOOLTIP);
+        configProperties.add(property1);
+        property1 = new ProviderConfigProperty();
+        property1.setName("full.path");
+        property1.setLabel("Full group path");
+        property1.setType(ProviderConfigProperty.BOOLEAN_TYPE);
+        property1.setDefaultValue("true");
+        property1.setHelpText("Include full path to group i.e. /top/level1/level2, false will just specify the group name");
+        configProperties.add(property1);
+
+        property1 = new ProviderConfigProperty();
+        property1.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN);
+        property1.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_LABEL);
+        property1.setType(ProviderConfigProperty.BOOLEAN_TYPE);
+        property1.setDefaultValue("true");
+        property1.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_HELP_TEXT);
+        configProperties.add(property1);
+        property1 = new ProviderConfigProperty();
+        property1.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN);
+        property1.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_LABEL);
+        property1.setType(ProviderConfigProperty.BOOLEAN_TYPE);
+        property1.setDefaultValue("true");
+        property1.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_HELP_TEXT);
+        configProperties.add(property1);
+
+
+
+    }
+
+    public static final String PROVIDER_ID = "oidc-group-membership-mapper";
+
+
+    public List<ProviderConfigProperty> getConfigProperties() {
+        return configProperties;
+    }
+
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+
+    @Override
+    public String getDisplayType() {
+        return "Group Membership";
+    }
+
+    @Override
+    public String getDisplayCategory() {
+        return TOKEN_MAPPER_CATEGORY;
+    }
+
+    @Override
+    public String getHelpText() {
+        return "Map user group membership";
+    }
+
+    public static boolean useFullPath(ProtocolMapperModel mappingModel) {
+        return "true".equals(mappingModel.getConfig().get("full.path"));
+    }
+
+
+    @Override
+    public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
+                                            UserSessionModel userSession, ClientSessionModel clientSession) {
+        if (!OIDCAttributeMapperHelper.includeInAccessToken(mappingModel)) return token;
+        buildMembership(token, mappingModel, userSession);
+        return token;
+    }
+
+    public void buildMembership(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) {
+        List<String> membership = new LinkedList<>();
+        boolean fullPath = useFullPath(mappingModel);
+        for (GroupModel group : userSession.getUser().getGroups()) {
+            if (fullPath) {
+                membership.add(ModelToRepresentation.buildGroupPath(group));
+            } else {
+                membership.add(group.getName());
+            }
+        }
+        String protocolClaim = mappingModel.getConfig().get(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME);
+
+        token.getOtherClaims().put(protocolClaim, membership);
+    }
+
+    @Override
+    public IDToken transformIDToken(IDToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
+        if (!OIDCAttributeMapperHelper.includeInIDToken(mappingModel)) return token;
+        buildMembership(token, mappingModel, userSession);
+        return token;
+    }
+
+    public static ProtocolMapperModel create(String name,
+                                      String tokenClaimName,
+                                      boolean consentRequired, String consentText,
+                                      boolean accessToken, boolean idToken) {
+        ProtocolMapperModel mapper = new ProtocolMapperModel();
+        mapper.setName(name);
+        mapper.setProtocolMapper(PROVIDER_ID);
+        mapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
+        mapper.setConsentRequired(consentRequired);
+        mapper.setConsentText(consentText);
+        Map<String, String> config = new HashMap<String, String>();
+        config.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, tokenClaimName);
+        if (accessToken) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true");
+        if (idToken) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true");
+        mapper.setConfig(config);
+        
+        return mapper;
+    }
+
+
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserAttributeMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserAttributeMapper.java
index 246b82e..3e96692 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserAttributeMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserAttributeMapper.java
@@ -6,6 +6,7 @@ import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.ProtocolMapperUtils;
 import org.keycloak.provider.ProviderConfigProperty;
 import org.keycloak.representations.AccessToken;
@@ -84,7 +85,7 @@ public class UserAttributeMapper extends AbstractOIDCProtocolMapper implements O
     protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) {
         UserModel user = userSession.getUser();
         String attributeName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_ATTRIBUTE);
-        List<String> attributeValue = user.getAttribute(attributeName);
+        List<String> attributeValue = KeycloakModelUtils.resolveAttribute(user, attributeName);
         if (attributeValue == null) return;
         OIDCAttributeMapperHelper.mapClaim(token, mappingModel, attributeValue);
     }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCClientDescriptionConverter.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCClientDescriptionConverter.java
index 56b47bb..6caacd6 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCClientDescriptionConverter.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCClientDescriptionConverter.java
@@ -5,9 +5,9 @@ import org.keycloak.exportimport.ClientDescriptionConverter;
 import org.keycloak.exportimport.ClientDescriptionConverterFactory;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
-import org.keycloak.models.utils.KeycloakModelUtils;
-import org.keycloak.protocol.oidc.representations.OIDCClientRepresentation;
+import org.keycloak.representations.oidc.OIDCClientRepresentation;
 import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.services.clientregistration.oidc.DescriptionConverter;
 import org.keycloak.util.JsonSerialization;
 
 import java.io.IOException;
@@ -17,6 +17,8 @@ import java.io.IOException;
  */
 public class OIDCClientDescriptionConverter implements ClientDescriptionConverter, ClientDescriptionConverterFactory {
 
+    public static final String ID = "openid-connect";
+
     @Override
     public boolean isSupported(String description) {
         description = description.trim();
@@ -26,15 +28,8 @@ public class OIDCClientDescriptionConverter implements ClientDescriptionConverte
     @Override
     public ClientRepresentation convertToInternal(String description) {
         try {
-            OIDCClientRepresentation oidcRep = JsonSerialization.readValue(description, OIDCClientRepresentation.class);
-
-            ClientRepresentation client = new ClientRepresentation();
-            client.setClientId(KeycloakModelUtils.generateId());
-            client.setName(oidcRep.getClientName());
-            client.setRedirectUris(oidcRep.getRedirectUris());
-            client.setBaseUrl(oidcRep.getClientUri());
-
-            return client;
+            OIDCClientRepresentation clientOIDC = JsonSerialization.readValue(description, OIDCClientRepresentation.class);
+            return DescriptionConverter.toInternal(clientOIDC);
         } catch (IOException e) {
             throw new RuntimeException(e);
         }
@@ -59,7 +54,7 @@ public class OIDCClientDescriptionConverter implements ClientDescriptionConverte
 
     @Override
     public String getId() {
-        return "openid-connect";
+        return ID;
     }
 
 }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java
index 714c0d1..60e9f9d 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java
@@ -4,6 +4,8 @@ import org.keycloak.OAuth2Constants;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
+import org.keycloak.services.clientregistration.ClientRegistrationService;
+import org.keycloak.services.clientregistration.oidc.OIDCClientRegistrationProviderFactory;
 import org.keycloak.services.resources.RealmsResource;
 import org.keycloak.services.Urls;
 import org.keycloak.wellknown.WellKnownProvider;
@@ -48,6 +50,7 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
         config.setUserinfoEndpoint(uriBuilder.clone().path(OIDCLoginProtocolService.class, "issueUserInfo").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
         config.setLogoutEndpoint(uriBuilder.clone().path(OIDCLoginProtocolService.class, "logout").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
         config.setJwksUri(uriBuilder.clone().path(OIDCLoginProtocolService.class, "certs").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
+        config.setRegistrationEndpoint(RealmsResource.clientRegistrationUrl(uriInfo).path(ClientRegistrationService.class, "provider").build(realm.getName(), OIDCClientRegistrationProviderFactory.ID).toString());
 
         config.setIdTokenSigningAlgValuesSupported(DEFAULT_ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED);
         config.setResponseTypesSupported(DEFAULT_RESPONSE_TYPES_SUPPORTED);
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java b/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java
index 9245e58..0226331 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java
@@ -7,7 +7,6 @@ import org.codehaus.jackson.annotate.JsonProperty;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -48,6 +47,9 @@ public class OIDCConfigurationRepresentation {
     @JsonProperty("response_modes_supported")
     private List<String> responseModesSupported;
 
+    @JsonProperty("registration_endpoint")
+    private String registrationEndpoint;
+
     protected Map<String, Object> otherClaims = new HashMap<String, Object>();
 
     public String getIssuer() {
@@ -138,6 +140,14 @@ public class OIDCConfigurationRepresentation {
         this.responseModesSupported = responseModesSupported;
     }
 
+    public String getRegistrationEndpoint() {
+        return registrationEndpoint;
+    }
+
+    public void setRegistrationEndpoint(String registrationEndpoint) {
+        this.registrationEndpoint = registrationEndpoint;
+    }
+
     @JsonAnyGetter
     public Map<String, Object> getOtherClaims() {
         return otherClaims;
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
index 17d5b23..32a9a6d 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -12,6 +12,7 @@ import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.jose.jws.crypto.RSAProvider;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.GroupModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.ProtocolMapperModel;
@@ -289,10 +290,23 @@ public class TokenManager {
         }
     }
 
+    public static void addGroupRoles(GroupModel group, Set<RoleModel> roleMappings) {
+        roleMappings.addAll(group.getRoleMappings());
+        if (group.getParentId() == null) return;
+        addGroupRoles(group.getParent(), roleMappings);
+    }
+
     public static Set<RoleModel> getAccess(String scopeParam, boolean applyScopeParam, ClientModel client, UserModel user) {
         Set<RoleModel> requestedRoles = new HashSet<RoleModel>();
 
-        Set<RoleModel> roleMappings = user.getRoleMappings();
+        Set<RoleModel> mappings = user.getRoleMappings();
+        Set<RoleModel> roleMappings = new HashSet<>();
+        roleMappings.addAll(mappings);
+        for (GroupModel group : user.getGroups()) {
+            addGroupRoles(group, roleMappings);
+        }
+
+
 
         if (client.isFullScopeAllowed()) {
             requestedRoles = roleMappings;
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/AuthorizeClientUtil.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/AuthorizeClientUtil.java
index c017ba3..a2d0d6c 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/utils/AuthorizeClientUtil.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/AuthorizeClientUtil.java
@@ -19,18 +19,8 @@ import javax.ws.rs.core.Response;
  */
 public class AuthorizeClientUtil {
 
-    public static ClientAuthResult authorizeClient(KeycloakSession session, EventBuilder event, RealmModel realm) {
-        AuthenticationFlowModel clientAuthFlow = realm.getClientAuthenticationFlow();
-        String flowId = clientAuthFlow.getId();
-
-        AuthenticationProcessor processor = new AuthenticationProcessor();
-        processor.setFlowId(flowId)
-                .setConnection(session.getContext().getConnection())
-                .setEventBuilder(event)
-                .setRealm(realm)
-                .setSession(session)
-                .setUriInfo(session.getContext().getUri())
-                .setRequest(session.getContext().getContextObject(HttpRequest.class));
+    public static ClientAuthResult authorizeClient(KeycloakSession session, EventBuilder event) {
+        AuthenticationProcessor processor = getAuthenticationProcessor(session, event);
 
         Response response = processor.authenticateClient();
         if (response != null) {
@@ -45,6 +35,24 @@ public class AuthorizeClientUtil {
         return new ClientAuthResult(client, processor.getClientAuthAttributes());
     }
 
+    public static AuthenticationProcessor getAuthenticationProcessor(KeycloakSession session, EventBuilder event) {
+        RealmModel realm = session.getContext().getRealm();
+
+        AuthenticationFlowModel clientAuthFlow = realm.getClientAuthenticationFlow();
+        String flowId = clientAuthFlow.getId();
+
+        AuthenticationProcessor processor = new AuthenticationProcessor();
+        processor.setFlowId(flowId)
+                .setConnection(session.getContext().getConnection())
+                .setEventBuilder(event)
+                .setRealm(realm)
+                .setSession(session)
+                .setUriInfo(session.getContext().getUri())
+                .setRequest(session.getContext().getContextObject(HttpRequest.class));
+
+        return processor;
+    }
+
     public static class ClientAuthResult {
 
         private final ClientModel client;
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java
new file mode 100644
index 0000000..0c95a37
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java
@@ -0,0 +1,125 @@
+package org.keycloak.services.clientregistration;
+
+import org.keycloak.events.EventBuilder;
+import org.keycloak.events.EventType;
+import org.keycloak.models.ClientInitialAccessModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.models.utils.RepresentationToModel;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.services.ErrorResponseException;
+import org.keycloak.services.ForbiddenException;
+
+import javax.ws.rs.core.Response;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public abstract class AbstractClientRegistrationProvider implements ClientRegistrationProvider {
+
+    protected KeycloakSession session;
+    protected EventBuilder event;
+    protected ClientRegistrationAuth auth;
+
+    public AbstractClientRegistrationProvider(KeycloakSession session) {
+        this.session = session;
+    }
+
+    public ClientRepresentation create(ClientRepresentation client) {
+        event.event(EventType.CLIENT_REGISTER);
+
+        auth.requireCreate();
+
+        try {
+            ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true);
+            if (client.getClientId() == null) {
+                clientModel.setClientId(clientModel.getId());
+            }
+
+            client = ModelToRepresentation.toRepresentation(clientModel);
+
+            String registrationAccessToken = ClientRegistrationTokenUtils.updateRegistrationAccessToken(session, clientModel);
+
+            client.setRegistrationAccessToken(registrationAccessToken);
+
+            if (auth.isInitialAccessToken()) {
+                ClientInitialAccessModel initialAccessModel = auth.getInitialAccessModel();
+                initialAccessModel.decreaseRemainingCount();
+            }
+
+            event.client(client.getClientId()).success();
+            return client;
+        } catch (ModelDuplicateException e) {
+            throw new ErrorResponseException(ErrorCodes.INVALID_CLIENT_METADATA, "Client Identifier in use", Response.Status.BAD_REQUEST);
+        }
+    }
+
+    public ClientRepresentation get(String clientId) {
+        event.event(EventType.CLIENT_INFO);
+
+        ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
+        auth.requireView(client);
+
+        ClientRepresentation rep = ModelToRepresentation.toRepresentation(client);
+
+        if (auth.isRegistrationAccessToken()) {
+            String registrationAccessToken = ClientRegistrationTokenUtils.updateRegistrationAccessToken(session, client);
+            rep.setRegistrationAccessToken(registrationAccessToken);
+        }
+
+        event.client(client.getClientId()).success();
+        return rep;
+    }
+
+    public ClientRepresentation update(String clientId, ClientRepresentation rep) {
+        event.event(EventType.CLIENT_UPDATE).client(clientId);
+
+        ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
+        auth.requireUpdate(client);
+
+        if (!client.getClientId().equals(rep.getClientId())) {
+            throw new ErrorResponseException(ErrorCodes.INVALID_CLIENT_METADATA, "Client Identifier modified", Response.Status.BAD_REQUEST);
+        }
+
+        RepresentationToModel.updateClient(rep, client);
+        rep = ModelToRepresentation.toRepresentation(client);
+
+        if (auth.isRegistrationAccessToken()) {
+            String registrationAccessToken = ClientRegistrationTokenUtils.updateRegistrationAccessToken(session, client);
+            rep.setRegistrationAccessToken(registrationAccessToken);
+        }
+
+        event.client(client.getClientId()).success();
+        return rep;
+    }
+
+    public void delete(String clientId) {
+        event.event(EventType.CLIENT_DELETE).client(clientId);
+
+        ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
+        auth.requireUpdate(client);
+
+        if (session.getContext().getRealm().removeClient(client.getId())) {
+            event.client(client.getClientId()).success();
+        } else {
+            throw new ForbiddenException();
+        }
+    }
+
+    @Override
+    public void setAuth(ClientRegistrationAuth auth) {
+        this.auth = auth;
+    }
+
+    @Override
+    public void setEvent(EventBuilder event) {
+        this.event = event;
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/AdapterInstallationClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/AdapterInstallationClientRegistrationProvider.java
new file mode 100644
index 0000000..c667b5e
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/clientregistration/AdapterInstallationClientRegistrationProvider.java
@@ -0,0 +1,91 @@
+package org.keycloak.services.clientregistration;
+
+import org.keycloak.authentication.AuthenticationProcessor;
+import org.keycloak.events.Errors;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.events.EventType;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
+import org.keycloak.services.ForbiddenException;
+import org.keycloak.services.managers.ClientManager;
+import org.keycloak.services.managers.RealmManager;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class AdapterInstallationClientRegistrationProvider implements ClientRegistrationProvider {
+
+    private KeycloakSession session;
+    private EventBuilder event;
+    private ClientRegistrationAuth auth;
+
+    public AdapterInstallationClientRegistrationProvider(KeycloakSession session) {
+        this.session = session;
+    }
+
+    @GET
+    @Path("{clientId}")
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response get(@PathParam("clientId") String clientId) {
+        event.event(EventType.CLIENT_INFO);
+
+        ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
+
+        if (auth.isAuthenticated()) {
+            auth.requireView(client);
+        } else {
+            authenticateClient(client);
+        }
+
+        ClientManager clientManager = new ClientManager(new RealmManager(session));
+        Object rep = clientManager.toInstallationRepresentation(session.getContext().getRealm(), client, session.getContext().getAuthServerUrl());
+
+        event.client(client.getClientId()).success();
+        return Response.ok(rep).build();
+    }
+
+    @Override
+    public void setAuth(ClientRegistrationAuth auth) {
+        this.auth = auth;
+    }
+
+    @Override
+    public void setEvent(EventBuilder event) {
+        this.event = event;
+    }
+
+    @Override
+    public void close() {
+    }
+
+    private void authenticateClient(ClientModel client) {
+        if (client.isPublicClient()) {
+            return;
+        }
+
+        AuthenticationProcessor processor = AuthorizeClientUtil.getAuthenticationProcessor(session, event);
+
+        Response response = processor.authenticateClient();
+        if (response != null) {
+            event.client(client.getClientId()).error(Errors.NOT_ALLOWED);
+            throw new ForbiddenException();
+        }
+
+        ClientModel authClient = processor.getClient();
+        if (client == null) {
+            event.client(client.getClientId()).error(Errors.NOT_ALLOWED);
+            throw new ForbiddenException();
+        }
+
+        if (!authClient.getClientId().equals(client.getClientId())) {
+            event.client(client.getClientId()).error(Errors.NOT_ALLOWED);
+            throw new ForbiddenException();
+        }
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationAuth.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationAuth.java
new file mode 100644
index 0000000..e5e71c5
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationAuth.java
@@ -0,0 +1,182 @@
+package org.keycloak.services.clientregistration;
+
+import org.jboss.resteasy.spi.UnauthorizedException;
+import org.keycloak.common.util.Time;
+import org.keycloak.events.Errors;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.models.*;
+import org.keycloak.representations.JsonWebToken;
+import org.keycloak.services.ForbiddenException;
+import org.keycloak.util.TokenUtil;
+
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.UriInfo;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientRegistrationAuth {
+
+    private KeycloakSession session;
+    private EventBuilder event;
+
+    private JsonWebToken jwt;
+    private ClientInitialAccessModel initialAccessModel;
+
+    public ClientRegistrationAuth(KeycloakSession session, EventBuilder event) {
+        this.session = session;
+        this.event = event;
+
+        init();
+    }
+
+    private void init() {
+        RealmModel realm = session.getContext().getRealm();
+        UriInfo uri = session.getContext().getUri();
+
+        String authorizationHeader = session.getContext().getRequestHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
+        if (authorizationHeader == null) {
+            return;
+        }
+
+        String[] split = authorizationHeader.split(" ");
+        if (!split[0].equalsIgnoreCase("bearer")) {
+            return;
+        }
+
+        jwt = ClientRegistrationTokenUtils.parseToken(realm, uri, split[1]);
+
+        if (isInitialAccessToken()) {
+            initialAccessModel = session.sessions().getClientInitialAccessModel(session.getContext().getRealm(), jwt.getId());
+            if (initialAccessModel == null) {
+                throw new ForbiddenException();
+            }
+        }
+    }
+
+    public boolean isAuthenticated() {
+        return jwt != null;
+    }
+
+    public boolean isBearerToken() {
+        return TokenUtil.TOKEN_TYPE_BEARER.equals(jwt.getType());
+    }
+
+    public boolean isInitialAccessToken() {
+        return ClientRegistrationTokenUtils.TYPE_INITIAL_ACCESS_TOKEN.equals(jwt.getType());
+    }
+
+    public boolean isRegistrationAccessToken() {
+        return ClientRegistrationTokenUtils.TYPE_REGISTRATION_ACCESS_TOKEN.equals(jwt.getType());
+    }
+
+    public void requireCreate() {
+        if (!isAuthenticated()) {
+            event.error(Errors.NOT_ALLOWED);
+            throw new UnauthorizedException();
+        }
+
+        if (isBearerToken()) {
+            if (hasRole(AdminRoles.MANAGE_CLIENTS, AdminRoles.CREATE_CLIENT)) {
+                return;
+            }
+        } else if (isInitialAccessToken()) {
+            if (initialAccessModel.getRemainingCount() > 0) {
+                if (initialAccessModel.getExpiration() == 0 || (initialAccessModel.getTimestamp() + initialAccessModel.getExpiration()) > Time.currentTime()) {
+                    return;
+                }
+            }
+        }
+
+        event.error(Errors.NOT_ALLOWED);
+        throw new ForbiddenException();
+    }
+
+    public void requireView(ClientModel client) {
+        if (!isAuthenticated()) {
+            event.error(Errors.NOT_ALLOWED);
+            throw new UnauthorizedException();
+        }
+
+        if (client == null) {
+            event.error(Errors.NOT_ALLOWED);
+            throw new ForbiddenException();
+        }
+
+        if (isBearerToken()) {
+            if (hasRole(AdminRoles.MANAGE_CLIENTS, AdminRoles.VIEW_CLIENTS)) {
+                return;
+            }
+        } else if (isRegistrationAccessToken()) {
+            if (client.getRegistrationToken() != null && client.getRegistrationToken().equals(jwt.getId())) {
+                return;
+            }
+        }
+
+        event.error(Errors.NOT_ALLOWED);
+        throw new ForbiddenException();
+    }
+
+    public void requireUpdate(ClientModel client) {
+        if (!isAuthenticated()) {
+            event.error(Errors.NOT_ALLOWED);
+            throw new UnauthorizedException();
+        }
+
+        if (client == null) {
+            event.error(Errors.NOT_ALLOWED);
+            throw new ForbiddenException();
+        }
+
+        if (isBearerToken()) {
+            if (hasRole(AdminRoles.MANAGE_CLIENTS)) {
+                return;
+            }
+        } else if (isRegistrationAccessToken()) {
+            if (client.getRegistrationToken() != null && client.getRegistrationToken().equals(jwt.getId())) {
+                return;
+            }
+        }
+
+        event.error(Errors.NOT_ALLOWED);
+        throw new ForbiddenException();
+    }
+
+    public ClientInitialAccessModel getInitialAccessModel() {
+        return initialAccessModel;
+    }
+
+    private boolean hasRole(String... role) {
+        try {
+            Map<String, Object> otherClaims = jwt.getOtherClaims();
+            if (otherClaims != null) {
+                Map<String, Map<String, List<String>>> resourceAccess = (Map<String, Map<String, List<String>>>) jwt.getOtherClaims().get("resource_access");
+                if (resourceAccess == null) {
+                    return false;
+                }
+
+                Map<String, List<String>> realmManagement = resourceAccess.get(Constants.REALM_MANAGEMENT_CLIENT_ID);
+                if (realmManagement == null) {
+                    return false;
+                }
+
+                List<String> resources = realmManagement.get("roles");
+                if (resources == null) {
+                    return false;
+                }
+
+                for (String r : role) {
+                    if (resources.contains(r)) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        } catch (Throwable t) {
+            return false;
+        }
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationProvider.java
index d1d6648..98bd6f9 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationProvider.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationProvider.java
@@ -1,7 +1,6 @@
 package org.keycloak.services.clientregistration;
 
 import org.keycloak.events.EventBuilder;
-import org.keycloak.models.RealmModel;
 import org.keycloak.provider.Provider;
 
 /**
@@ -9,7 +8,7 @@ import org.keycloak.provider.Provider;
  */
 public interface ClientRegistrationProvider extends Provider {
 
-    void setRealm(RealmModel realm);
+    void setAuth(ClientRegistrationAuth auth);
 
     void setEvent(EventBuilder event);
 
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java
index 8b215fa..621d5fb 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java
@@ -1,37 +1,50 @@
 package org.keycloak.services.clientregistration;
 
+import org.jboss.resteasy.spi.NotFoundException;
 import org.keycloak.events.EventBuilder;
 import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.RealmModel;
-import org.keycloak.services.managers.AppAuthManager;
+import org.keycloak.services.ErrorResponseException;
 
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
 public class ClientRegistrationService {
 
-    private RealmModel realm;
-
     private EventBuilder event;
 
     @Context
     private KeycloakSession session;
 
-    public ClientRegistrationService(RealmModel realm, EventBuilder event) {
-        this.realm = realm;
+    public ClientRegistrationService(EventBuilder event) {
         this.event = event;
     }
 
     @Path("{provider}")
-    public Object getProvider(@PathParam("provider") String providerId) {
+    public Object provider(@PathParam("provider") String providerId) {
+        checkSsl();
+
         ClientRegistrationProvider provider = session.getProvider(ClientRegistrationProvider.class, providerId);
-        provider.setRealm(realm);
+
+        if (provider == null) {
+            throw new NotFoundException("Client registration provider not found");
+        }
+
         provider.setEvent(event);
+        provider.setAuth(new ClientRegistrationAuth(session, event));
         return provider;
     }
 
+    private void checkSsl() {
+        if (!session.getContext().getUri().getBaseUri().getScheme().equals("https")) {
+            if (session.getContext().getRealm().getSslRequired().isRequired(session.getContext().getConnection())) {
+                throw new ErrorResponseException("invalid_request", "HTTPS required", Response.Status.FORBIDDEN);
+            }
+        }
+    }
+
 }
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationTokenUtils.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationTokenUtils.java
new file mode 100644
index 0000000..241bcff
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationTokenUtils.java
@@ -0,0 +1,99 @@
+package org.keycloak.services.clientregistration;
+
+import org.keycloak.common.util.Time;
+import org.keycloak.jose.jws.JWSBuilder;
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.crypto.RSAProvider;
+import org.keycloak.models.ClientInitialAccessModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.representations.JsonWebToken;
+import org.keycloak.services.ForbiddenException;
+import org.keycloak.services.Urls;
+import org.keycloak.util.TokenUtil;
+
+import javax.ws.rs.core.UriInfo;
+import java.io.IOException;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientRegistrationTokenUtils {
+
+    public static final String TYPE_INITIAL_ACCESS_TOKEN = "InitialAccessToken";
+    public static final String TYPE_REGISTRATION_ACCESS_TOKEN = "RegistrationAccessToken";
+
+    public static String updateRegistrationAccessToken(KeycloakSession session, ClientModel client) {
+        return updateRegistrationAccessToken(session.getContext().getRealm(), session.getContext().getUri(), client);
+    }
+
+    public static String updateRegistrationAccessToken(RealmModel realm, UriInfo uri, ClientModel client) {
+        String id = KeycloakModelUtils.generateId();
+        client.setRegistrationToken(id);
+        String token = createToken(realm, uri, id, TYPE_REGISTRATION_ACCESS_TOKEN, 0);
+        return token;
+    }
+
+    public static String createInitialAccessToken(RealmModel realm, UriInfo uri, ClientInitialAccessModel model) {
+        return createToken(realm, uri, model.getId(), TYPE_INITIAL_ACCESS_TOKEN, model.getExpiration() > 0 ? model.getTimestamp() + model.getExpiration() : 0);
+    }
+
+    public static JsonWebToken parseToken(RealmModel realm, UriInfo uri, String token) {
+        JWSInput input;
+        try {
+            input = new JWSInput(token);
+        } catch (Exception e) {
+            throw new ForbiddenException(e);
+        }
+
+        if (!RSAProvider.verify(input, realm.getPublicKey())) {
+            throw new ForbiddenException("Invalid signature");
+        }
+
+        JsonWebToken jwt;
+        try {
+            jwt = input.readJsonContent(JsonWebToken.class);
+        } catch (IOException e) {
+            throw new ForbiddenException(e);
+        }
+
+        if (!getIssuer(realm, uri).equals(jwt.getIssuer())) {
+            throw new ForbiddenException("Issuer doesn't match");
+        }
+
+        if (!jwt.isActive()) {
+            throw new ForbiddenException("Expired token");
+        }
+
+        if (!(TokenUtil.TOKEN_TYPE_BEARER.equals(jwt.getType()) ||
+                TYPE_INITIAL_ACCESS_TOKEN.equals(jwt.getType()) ||
+                TYPE_REGISTRATION_ACCESS_TOKEN.equals(jwt.getType()))) {
+            throw new ForbiddenException("Invalid token type");
+        }
+
+        return jwt;
+    }
+
+    private static String createToken(RealmModel realm, UriInfo uri, String id, String type, int expiration) {
+        JsonWebToken jwt = new JsonWebToken();
+
+        String issuer = getIssuer(realm, uri);
+
+        jwt.type(type);
+        jwt.id(id);
+        jwt.issuedAt(Time.currentTime());
+        jwt.expiration(expiration);
+        jwt.issuer(issuer);
+        jwt.audience(issuer);
+
+        String token = new JWSBuilder().jsonContent(jwt).rsa256(realm.getPrivateKey());
+        return token;
+    }
+
+    private static String getIssuer(RealmModel realm, UriInfo uri) {
+        return Urls.realmIssuer(uri.getBaseUri(), realm.getName());
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java
index 04cb46a..126d00a 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java
@@ -1,22 +1,14 @@
 package org.keycloak.services.clientregistration;
 
-import org.jboss.logging.Logger;
-import org.keycloak.events.Errors;
 import org.keycloak.events.EventBuilder;
 import org.keycloak.events.EventType;
-import org.keycloak.models.*;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.models.utils.RepresentationToModel;
-import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
-import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.idm.ClientRepresentation;
-import org.keycloak.services.ErrorResponse;
-import org.keycloak.services.ForbiddenException;
-import org.keycloak.services.managers.AppAuthManager;
-import org.keycloak.services.managers.AuthenticationManager;
 
 import javax.ws.rs.*;
-import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import java.net.URI;
@@ -24,132 +16,55 @@ import java.net.URI;
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class DefaultClientRegistrationProvider implements ClientRegistrationProvider {
-
-    private static final Logger logger = Logger.getLogger(DefaultClientRegistrationProvider.class);
-
-    private KeycloakSession session;
-    private EventBuilder event;
-    private RealmModel realm;
+public class DefaultClientRegistrationProvider extends AbstractClientRegistrationProvider {
 
     public DefaultClientRegistrationProvider(KeycloakSession session) {
-        this.session = session;
+        super(session);
     }
 
-
     @POST
     @Consumes(MediaType.APPLICATION_JSON)
-    public Response create(ClientRepresentation client) {
-        event.event(EventType.CLIENT_REGISTER);
-
-        authenticate(true, null);
-
-        try {
-            ClientModel clientModel = RepresentationToModel.createClient(session, realm, client, true);
-            client = ModelToRepresentation.toRepresentation(clientModel);
-            URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build();
-
-            logger.infov("Created client {0}", client.getClientId());
-
-            event.client(client.getClientId()).success();
-            return Response.created(uri).entity(client).build();
-        } catch (ModelDuplicateException e) {
-            return ErrorResponse.exists("Client " + client.getClientId() + " already exists");
-        }
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response createDefault(ClientRepresentation client) {
+        client = create(client);
+        URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(client.getClientId()).build();
+        return Response.created(uri).entity(client).build();
     }
 
     @GET
     @Path("{clientId}")
     @Produces(MediaType.APPLICATION_JSON)
-    public Response get(@PathParam("clientId") String clientId) {
-        event.event(EventType.CLIENT_INFO);
-
-        ClientModel client = authenticate(false, clientId);
-        if (client == null) {
-            return Response.status(Response.Status.NOT_FOUND).build();
-        }
-        return Response.ok(ModelToRepresentation.toRepresentation(client)).build();
+    public Response getDefault(@PathParam("clientId") String clientId) {
+        ClientRepresentation client = get(clientId);
+        return Response.ok(client).build();
     }
 
     @PUT
     @Path("{clientId}")
     @Consumes(MediaType.APPLICATION_JSON)
-    public Response update(@PathParam("clientId") String clientId, ClientRepresentation rep) {
-        event.event(EventType.CLIENT_UPDATE).client(clientId);
-
-        ClientModel client = authenticate(false, clientId);
-        RepresentationToModel.updateClient(rep, client);
-
-        logger.infov("Updated client {0}", rep.getClientId());
-
-        event.success();
-        return Response.status(Response.Status.OK).build();
+    public Response updateDefault(@PathParam("clientId") String clientId, ClientRepresentation client) {
+        client = update(clientId, client);
+        return Response.ok(client).build();
     }
 
     @DELETE
     @Path("{clientId}")
-    public Response delete(@PathParam("clientId") String clientId) {
-        event.event(EventType.CLIENT_DELETE).client(clientId);
-
-        ClientModel client = authenticate(false, clientId);
-        if (realm.removeClient(client.getId())) {
-            event.success();
-            return Response.ok().build();
-        } else {
-            return Response.status(Response.Status.NOT_FOUND).build();
-        }
+    public void deleteDefault(@PathParam("clientId") String clientId) {
+        delete(clientId);
     }
 
     @Override
-    public void close() {
-
-    }
-
-
-    private ClientModel authenticate(boolean create, String clientId) {
-        String authorizationHeader = session.getContext().getRequestHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
-
-        boolean bearer = authorizationHeader != null && authorizationHeader.split(" ")[0].equalsIgnoreCase("Bearer");
-
-        if (bearer) {
-            AuthenticationManager.AuthResult authResult = new AppAuthManager().authenticateBearerToken(session, realm);
-            AccessToken.Access realmAccess = authResult.getToken().getResourceAccess(Constants.REALM_MANAGEMENT_CLIENT_ID);
-            if (realmAccess != null) {
-                if (realmAccess.isUserInRole(AdminRoles.MANAGE_CLIENTS)) {
-                    return create ? null : realm.getClientByClientId(clientId);
-                }
-
-                if (create && realmAccess.isUserInRole(AdminRoles.CREATE_CLIENT)) {
-                    return create ? null : realm.getClientByClientId(clientId);
-                }
-            }
-        } else if (!create) {
-            ClientModel client;
-
-            try {
-                AuthorizeClientUtil.ClientAuthResult clientAuth = AuthorizeClientUtil.authorizeClient(session, event, realm);
-                client = clientAuth.getClient();
-
-                if (client != null && !client.isPublicClient() && client.getClientId().equals(clientId)) {
-                    return client;
-                }
-            } catch (Throwable t) {
-            }
-        }
-
-        event.error(Errors.NOT_ALLOWED);
-
-        throw new ForbiddenException();
+    public void setAuth(ClientRegistrationAuth auth) {
+        this.auth = auth;
     }
 
     @Override
-    public void setRealm(RealmModel realm) {
-this.realm = realm;
+    public void setEvent(EventBuilder event) {
+        this.event = event;
     }
 
     @Override
-    public void setEvent(EventBuilder event) {
-        this.event = event;
+    public void close() {
     }
 
 }
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ErrorCodes.java b/services/src/main/java/org/keycloak/services/clientregistration/ErrorCodes.java
new file mode 100644
index 0000000..ed491de
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/clientregistration/ErrorCodes.java
@@ -0,0 +1,12 @@
+package org.keycloak.services.clientregistration;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface ErrorCodes {
+
+    String INVALID_REDIRECT_URI = "invalid_redirect_uri";
+
+    String INVALID_CLIENT_METADATA = "invalid_client_metadata";
+
+}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java
new file mode 100644
index 0000000..a7f9f2c
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java
@@ -0,0 +1,34 @@
+package org.keycloak.services.clientregistration.oidc;
+
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.oidc.OIDCClientRepresentation;
+
+import java.net.URI;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class DescriptionConverter {
+
+    public static ClientRepresentation toInternal(OIDCClientRepresentation clientOIDC) {
+        ClientRepresentation client = new ClientRepresentation();
+        client.setClientId(clientOIDC.getClientId());
+        client.setName(clientOIDC.getClientName());
+        client.setRedirectUris(clientOIDC.getRedirectUris());
+        client.setBaseUrl(clientOIDC.getClientUri());
+        return client;
+    }
+
+    public static OIDCClientRepresentation toExternalResponse(ClientRepresentation client, URI uri) {
+        OIDCClientRepresentation response = new OIDCClientRepresentation();
+        response.setClientId(client.getClientId());
+        response.setClientName(client.getName());
+        response.setClientUri(client.getBaseUrl());
+        response.setClientSecret(client.getSecret());
+        response.setRedirectUris(client.getRedirectUris());
+        response.setRegistrationAccessToken(client.getRegistrationAccessToken());
+        response.setRegistrationClientUri(uri.toString());
+        return response;
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java
new file mode 100644
index 0000000..e60720b
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java
@@ -0,0 +1,84 @@
+package org.keycloak.services.clientregistration.oidc;
+
+import org.keycloak.common.util.Time;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.representations.oidc.OIDCClientRepresentation;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.services.ErrorResponseException;
+import org.keycloak.services.clientregistration.AbstractClientRegistrationProvider;
+import org.keycloak.services.clientregistration.ClientRegistrationAuth;
+import org.keycloak.services.clientregistration.ErrorCodes;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.net.URI;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class OIDCClientRegistrationProvider extends AbstractClientRegistrationProvider {
+
+    public OIDCClientRegistrationProvider(KeycloakSession session) {
+        super(session);
+    }
+
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response createOIDC(OIDCClientRepresentation clientOIDC) {
+        if (clientOIDC.getClientId() != null) {
+            throw new ErrorResponseException(ErrorCodes.INVALID_CLIENT_METADATA, "Client Identifier included", Response.Status.BAD_REQUEST);
+        }
+
+        ClientRepresentation client = DescriptionConverter.toInternal(clientOIDC);
+        client = create(client);
+        URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(client.getClientId()).build();
+        clientOIDC = DescriptionConverter.toExternalResponse(client, uri);
+        clientOIDC.setClientIdIssuedAt(Time.currentTime());
+        return Response.created(uri).entity(clientOIDC).build();
+    }
+
+    @GET
+    @Path("{clientId}")
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response getOIDC(@PathParam("clientId") String clientId) {
+        ClientRepresentation client = get(clientId);
+        OIDCClientRepresentation clientOIDC = DescriptionConverter.toExternalResponse(client, session.getContext().getUri().getRequestUri());
+        return Response.ok(clientOIDC).build();
+    }
+
+    @PUT
+    @Path("{clientId}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response updateOIDC(@PathParam("clientId") String clientId, OIDCClientRepresentation clientOIDC) {
+        ClientRepresentation client = DescriptionConverter.toInternal(clientOIDC);
+        client = update(clientId, client);
+        URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(client.getClientId()).build();
+        clientOIDC = DescriptionConverter.toExternalResponse(client, uri);
+        return Response.ok(clientOIDC).build();
+    }
+
+    @DELETE
+    @Path("{clientId}")
+    public void deleteOIDC(@PathParam("clientId") String clientId) {
+        delete(clientId);
+    }
+
+    @Override
+    public void setAuth(ClientRegistrationAuth auth) {
+        this.auth = auth;
+    }
+
+    @Override
+    public void setEvent(EventBuilder event) {
+        this.event = event;
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java
index a78f99e..26aba4b 100755
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java
@@ -10,6 +10,7 @@ import org.keycloak.services.util.LocaleHelper;
 
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.UriInfo;
+import java.net.URI;
 import java.util.Locale;
 
 /**
@@ -30,6 +31,13 @@ public class DefaultKeycloakContext implements KeycloakContext {
     }
 
     @Override
+    public URI getAuthServerUrl() {
+        UriInfo uri = getUri();
+        KeycloakApplication keycloakApplication = getContextObject(KeycloakApplication.class);
+        return keycloakApplication.getBaseUri(uri);
+    }
+
+    @Override
     public String getContextPath() {
         KeycloakApplication app = getContextObject(KeycloakApplication.class);
         return app.getContextPath();
diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index 688c242..d44a622 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -50,6 +50,7 @@ import org.keycloak.timer.TimerProvider;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
+import static java.lang.Boolean.TRUE;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.protocol.ProtocolMapperUtils;
 
@@ -221,9 +222,12 @@ public class RealmManager implements RealmImporter {
         if(rep.getEnabledEventTypes() != null) {
             realm.setEnabledEventTypes(new HashSet<>(rep.getEnabledEventTypes()));
         }
-
-        realm.setAdminEventsEnabled(rep.isAdminEventsEnabled());
-        realm.setAdminEventsDetailsEnabled(rep.isAdminEventsDetailsEnabled());
+        if(rep.isAdminEventsEnabled() != null) {
+            realm.setAdminEventsEnabled(rep.isAdminEventsEnabled());
+        }
+        if(rep.isAdminEventsDetailsEnabled() != null){
+            realm.setAdminEventsDetailsEnabled(rep.isAdminEventsDetailsEnabled());
+        }
     }
 
     private void setupMasterAdminManagement(RealmModel realm) {
diff --git a/services/src/main/java/org/keycloak/services/messages/Messages.java b/services/src/main/java/org/keycloak/services/messages/Messages.java
index d092d16..5f2eeb3 100755
--- a/services/src/main/java/org/keycloak/services/messages/Messages.java
+++ b/services/src/main/java/org/keycloak/services/messages/Messages.java
@@ -64,9 +64,13 @@ public class Messages {
 
     public static final String EMAIL_EXISTS = "emailExistsMessage";
 
-    public static final String FEDERATED_IDENTITY_EMAIL_EXISTS = "federatedIdentityEmailExistsMessage";
+    public static final String FEDERATED_IDENTITY_EXISTS = "federatedIdentityExistsMessage";
 
-    public static final String FEDERATED_IDENTITY_USERNAME_EXISTS = "federatedIdentityUsernameExistsMessage";
+    public static final String FEDERATED_IDENTITY_CONFIRM_LINK_MESSAGE = "federatedIdentityConfirmLinkMessage";
+
+    public static final String FEDERATED_IDENTITY_CONFIRM_REAUTHENTICATE_MESSAGE = "federatedIdentityConfirmReauthenticateMessage";
+
+    public static final String IDENTITY_PROVIDER_DIFFERENT_USER_MESSAGE = "identityProviderDifferentUserMessage";
 
     public static final String CONFIGURE_TOTP = "configureTotpMessage";
 
@@ -76,6 +80,8 @@ public class Messages {
 
     public static final String VERIFY_EMAIL = "verifyEmailMessage";
 
+    public static final String LINK_IDP = "linkIdpMessage";
+
     public static final String EMAIL_VERIFIED = "emailVerifiedMessage";
 
     public static final String EMAIL_SENT = "emailSentMessage";
@@ -147,6 +153,8 @@ public class Messages {
 
     public static final String IDENTITY_PROVIDER_NOT_FOUND = "identityProviderNotFoundMessage";
 
+    public static final String IDENTITY_PROVIDER_LINK_SUCCESS = "identityProviderLinkSuccess";
+
     public static final String IDENTITY_PROVIDER_NOT_UNIQUE = "identityProviderNotUniqueMessage";
 
     public static final String REALM_SUPPORTS_NO_CREDENTIALS = "realmSupportsNoCredentialsMessage";
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index dc5fa8d..af24034 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -61,6 +61,7 @@ import org.keycloak.services.messages.Messages;
 import org.keycloak.services.util.ResolveRelative;
 import org.keycloak.services.validation.Validation;
 import org.keycloak.common.util.UriUtils;
+import org.keycloak.util.JsonSerialization;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
@@ -74,6 +75,8 @@ import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
 import javax.ws.rs.core.Variant;
+
+import java.io.IOException;
 import java.lang.reflect.Method;
 import java.net.URI;
 import java.util.HashSet;
@@ -116,6 +119,9 @@ public class AccountService extends AbstractSecuredLocalService {
 
     public static final String KEYCLOAK_STATE_CHECKER = "KEYCLOAK_STATE_CHECKER";
 
+    // Used when some other context (ie. IdentityBrokerService) wants to forward error to account management and display it here
+    public static final String ACCOUNT_MGMT_FORWARDED_ERROR_NOTE = "ACCOUNT_MGMT_FORWARDED_ERROR";
+
     private final AppAuthManager authManager;
     private EventBuilder event;
     private AccountProvider account;
@@ -217,6 +223,17 @@ public class AccountService extends AbstractSecuredLocalService {
 
             setReferrerOnPage();
 
+            String forwardedError = auth.getClientSession().getNote(ACCOUNT_MGMT_FORWARDED_ERROR_NOTE);
+            if (forwardedError != null) {
+                try {
+                    FormMessage errorMessage = JsonSerialization.readValue(forwardedError, FormMessage.class);
+                    account.setError(errorMessage.getMessage(), errorMessage.getParameters());
+                    auth.getClientSession().removeNote(ACCOUNT_MGMT_FORWARDED_ERROR_NOTE);
+                } catch (IOException ioe) {
+                    throw new RuntimeException(ioe);
+                }
+            }
+
             return account.createResponse(page);
         } else {
             return login(path);
@@ -366,7 +383,7 @@ public class AccountService extends AbstractSecuredLocalService {
 
         UserModel user = auth.getUser();
 
-        List<FormMessage> errors = Validation.validateUpdateProfileForm(realm, formData);
+        List<FormMessage> errors = Validation.validateUpdateProfileForm(realm.isEditUsernameAllowed(), formData);
         if (errors != null && !errors.isEmpty()) {
             setReferrerOnPage();
             return account.setErrors(errors).setProfileFormData(formData).createResponse(AccountPages.ACCOUNT);
@@ -724,7 +741,9 @@ public class AccountService extends AbstractSecuredLocalService {
                         logger.debugv("Social provider {0} removed successfully from user {1}", providerId, user.getUsername());
 
                         event.event(EventType.REMOVE_FEDERATED_IDENTITY).client(auth.getClient()).user(auth.getUser())
-                                .detail(Details.USERNAME, link.getUserId() + "@" + link.getIdentityProvider())
+                                .detail(Details.USERNAME, auth.getUser().getUsername())
+                                .detail(Details.IDENTITY_PROVIDER, link.getIdentityProvider())
+                                .detail(Details.IDENTITY_PROVIDER_USERNAME, link.getUserName())
                                 .success();
 
                         setReferrerOnPage();
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java
index 871bf05..5c22200 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java
@@ -40,12 +40,7 @@ import javax.ws.rs.ext.Providers;
 import java.io.IOException;
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
+import java.util.*;
 import javax.ws.rs.QueryParam;
 
 /**
@@ -318,10 +313,10 @@ public class AdminConsole {
         }
 
         try {
-            Properties msgs = AdminMessagesLoader.getMessages(getTheme(), lang);
+            Properties msgs = getTheme().getMessages("admin-messages", Locale.forLanguageTag(lang));
             if (msgs.isEmpty()) {
                 logger.warn("Message bundle not found for language code '" + lang + "'");
-                msgs = AdminMessagesLoader.getMessages(getTheme(), "en"); // fall back to en
+                msgs = getTheme().getMessages("admin-messages", Locale.ENGLISH);
             }
 
             if (msgs.isEmpty()) logger.fatal("Message bundle not found for language code 'en'");
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java
index 063e66c..9ff6807 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java
@@ -276,6 +276,10 @@ public class AuthenticationManagementResource {
     @Consumes(MediaType.APPLICATION_JSON)
     public Response createFlow(AuthenticationFlowModel model) {
         this.auth.requireManage();
+        
+        if (model.getAlias() == null || model.getAlias().isEmpty()) {
+            return ErrorResponse.exists("Failed to create flow with empty alias name");
+        }
 
         if (realm.getFlowByAlias(model.getAlias()) != null) {
             return ErrorResponse.exists("Flow " + model.getAlias() + " already exists");
@@ -711,6 +715,12 @@ public class AuthenticationManagementResource {
         if (parentFlow.isBuiltIn()) {
             throw new BadRequestException("It is illegal to remove execution from a built in flow");
         }
+        
+        if(model.getFlowId() != null) {
+        	AuthenticationFlowModel nonTopLevelFlow = realm.getAuthenticationFlowById(model.getFlowId());
+        	realm.removeAuthenticationFlow(nonTopLevelFlow);
+        }
+		
         realm.removeAuthenticatorExecution(model);
     }
 
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java
new file mode 100644
index 0000000..7ac2036
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java
@@ -0,0 +1,102 @@
+package org.keycloak.services.resources.admin;
+
+import org.keycloak.events.admin.OperationType;
+import org.keycloak.models.ClientInitialAccessModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
+import org.keycloak.representations.idm.ClientInitialAccessPresentation;
+import org.keycloak.services.clientregistration.ClientRegistrationTokenUtils;
+
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.*;
+import javax.ws.rs.core.*;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientInitialAccessResource {
+
+    private final RealmAuth auth;
+    private final RealmModel realm;
+    private final AdminEventBuilder adminEvent;
+
+    @Context
+    protected KeycloakSession session;
+
+    @Context
+    protected UriInfo uriInfo;
+
+    public ClientInitialAccessResource(RealmModel realm, RealmAuth auth, AdminEventBuilder adminEvent) {
+        this.auth = auth;
+        this.realm = realm;
+        this.adminEvent = adminEvent;
+
+        auth.init(RealmAuth.Resource.CLIENT);
+    }
+
+    /**
+     * Create a new initial access token.
+     *
+     * @param config
+     * @return
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public ClientInitialAccessPresentation create(ClientInitialAccessCreatePresentation config, @Context final HttpServletResponse response) {
+        auth.requireManage();
+
+        int expiration = config.getExpiration() != null ? config.getExpiration() : 0;
+        int count = config.getCount() != null ? config.getCount() : 1;
+
+        ClientInitialAccessModel clientInitialAccessModel = session.sessions().createClientInitialAccessModel(realm, expiration, count);
+
+        adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, clientInitialAccessModel.getId()).representation(config).success();
+
+        if (session.getTransaction().isActive()) {
+            session.getTransaction().commit();
+        }
+
+        ClientInitialAccessPresentation rep = wrap(clientInitialAccessModel);
+
+        String token = ClientRegistrationTokenUtils.createInitialAccessToken(realm, uriInfo, clientInitialAccessModel);
+        rep.setToken(token);
+
+        response.setStatus(Response.Status.CREATED.getStatusCode());
+        response.setHeader(HttpHeaders.LOCATION, uriInfo.getAbsolutePathBuilder().path(clientInitialAccessModel.getId()).build().toString());
+
+        return rep;
+    }
+    
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    public List<ClientInitialAccessPresentation> list() {
+        List<ClientInitialAccessModel> models = session.sessions().listClientInitialAccess(realm);
+        List<ClientInitialAccessPresentation> reps = new LinkedList<>();
+        for (ClientInitialAccessModel m : models) {
+            ClientInitialAccessPresentation r = wrap(m);
+            reps.add(r);
+        }
+        return reps;
+    }
+
+    @DELETE
+    @Path("{id}")
+    public void delete(final @PathParam("id") String id) {
+        session.sessions().removeClientInitialAccessModel(realm, id);
+    }
+
+    private ClientInitialAccessPresentation wrap(ClientInitialAccessModel model) {
+        ClientInitialAccessPresentation rep = new ClientInitialAccessPresentation();
+        rep.setId(model.getId());
+        rep.setTimestamp(model.getTimestamp());
+        rep.setExpiration(model.getExpiration());
+        rep.setCount(model.getCount());
+        rep.setRemainingCount(model.getRemainingCount());
+        return rep;
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
index 5d76778..03b0636 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
@@ -22,6 +22,7 @@ import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
 import org.keycloak.representations.idm.UserSessionRepresentation;
+import org.keycloak.services.clientregistration.ClientRegistrationTokenUtils;
 import org.keycloak.services.managers.ClientManager;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.services.managers.ResourceAdminManager;
@@ -50,6 +51,8 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import static java.lang.Boolean.TRUE;
+
 
 /**
  * Base resource class for managing one particular client of a realm.
@@ -103,7 +106,7 @@ public class ClientResource {
         auth.requireManage();
 
         try {
-            if (rep.isServiceAccountsEnabled() && !client.isServiceAccountsEnabled()) {
+            if (TRUE.equals(rep.isServiceAccountsEnabled()) && !client.isServiceAccountsEnabled()) {
                 new ClientManager(new RealmManager(session)).enableServiceAccount(client);;
             }
 
@@ -215,6 +218,27 @@ public class ClientResource {
     }
 
     /**
+     * Generate a new registration access token for the client
+     *
+     * @return
+     */
+    @Path("registration-access-token")
+    @POST
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public ClientRepresentation regenerateRegistrationAccessToken() {
+        auth.requireManage();
+
+        String token = ClientRegistrationTokenUtils.updateRegistrationAccessToken(realm, uriInfo, client);
+
+        ClientRepresentation rep = ModelToRepresentation.toRepresentation(client);
+        rep.setRegistrationAccessToken(token);
+
+        adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).representation(rep).success();
+        return rep;
+    }
+
+    /**
      * Get the client secret
      *
      * @return
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java b/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java
new file mode 100755
index 0000000..668057e
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java
@@ -0,0 +1,182 @@
+package org.keycloak.services.resources.admin;
+
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.annotations.cache.NoCache;
+import org.jboss.resteasy.spi.NotFoundException;
+import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.representations.idm.GroupRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Bill Burke
+ */
+public class GroupResource {
+
+    private static Logger logger = Logger.getLogger(GroupResource.class);
+
+    private final RealmModel realm;
+    private final KeycloakSession session;
+    private final RealmAuth auth;
+    private final AdminEventBuilder adminEvent;
+    private final GroupModel group;
+
+    public GroupResource(RealmModel realm, GroupModel group, KeycloakSession session, RealmAuth auth, AdminEventBuilder adminEvent) {
+        this.realm = realm;
+        this.session = session;
+        this.auth = auth;
+        this.adminEvent = adminEvent;
+        this.group = group;
+    }
+
+    @Context private UriInfo uriInfo;
+
+    /**
+     *
+     *
+     * @return
+     */
+    @GET
+    @NoCache
+    @Produces(MediaType.APPLICATION_JSON)
+    public GroupRepresentation getGroup() {
+        this.auth.requireView();
+        return ModelToRepresentation.toGroupHierarchy(group, true);
+    }
+
+    /**
+     * Update group, ignores subgroups.
+     *
+     * @param rep
+     */
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void updateGroup(GroupRepresentation rep) {
+        updateGroup(rep, group);
+
+
+    }
+
+    @DELETE
+    public void deleteGroup() {
+        realm.removeGroup(group);
+    }
+
+
+    /**
+     * Set or create child.  This will just set the parent if it exists.  Create it and set the parent
+     * if the group doesn't exist.
+     *
+     * @param rep
+     */
+    @POST
+    @Path("children")
+    @NoCache
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response addChild(GroupRepresentation rep) {
+        Response.ResponseBuilder builder = Response.status(204);
+        GroupModel child = null;
+        if (rep.getId() != null) {
+            child = realm.getGroupById(rep.getId());
+            if (child == null) {
+                throw new NotFoundException("Could not find child by id");
+            }
+        } else {
+            child = realm.createGroup(rep.getName());
+            updateGroup(rep, child);
+            URI uri = uriInfo.getBaseUriBuilder()
+                                           .path(uriInfo.getMatchedURIs().get(1))
+                                           .path(child.getId()).build();
+            builder.status(201).location(uri);
+
+        }
+        realm.moveGroup(child, group);
+        GroupRepresentation childRep = ModelToRepresentation.toGroupHierarchy(child, true);
+        return builder.type(MediaType.APPLICATION_JSON_TYPE).entity(childRep).build();
+    }
+
+    public static void updateGroup(GroupRepresentation rep, GroupModel model) {
+        if (rep.getName() != null) model.setName(rep.getName());
+
+        if (rep.getAttributes() != null) {
+            Set<String> attrsToRemove = new HashSet<>(model.getAttributes().keySet());
+            attrsToRemove.removeAll(rep.getAttributes().keySet());
+            for (Map.Entry<String, List<String>> attr : rep.getAttributes().entrySet()) {
+                model.setAttribute(attr.getKey(), attr.getValue());
+            }
+
+            for (String attr : attrsToRemove) {
+                model.removeAttribute(attr);
+            }
+        }
+    }
+
+    @Path("role-mappings")
+    public RoleMapperResource getRoleMappings() {
+        auth.init(RealmAuth.Resource.USER);
+
+        RoleMapperResource resource =  new RoleMapperResource(realm, auth, group, adminEvent);
+        ResteasyProviderFactory.getInstance().injectProperties(resource);
+        return resource;
+
+    }
+
+    /**
+     * Get users
+     *
+     * Returns a list of users, filtered according to query parameters
+     *
+     * @param firstResult Pagination offset
+     * @param maxResults Pagination size
+     * @return
+     */
+    @GET
+    @NoCache
+    @Path("members")
+    @Produces(MediaType.APPLICATION_JSON)
+    public List<UserRepresentation> getMembers(@QueryParam("first") Integer firstResult,
+                                               @QueryParam("max") Integer maxResults) {
+        auth.requireView();
+        firstResult = firstResult != null ? firstResult : -1;
+        maxResults = maxResults != null ? maxResults : -1;
+
+        List<UserRepresentation> results = new ArrayList<UserRepresentation>();
+        List<UserModel> userModels = session.users().getGroupMembers(realm, group, firstResult, maxResults);
+
+        for (UserModel user : userModels) {
+            results.add(ModelToRepresentation.toRepresentation(user));
+        }
+        return results;
+    }
+
+
+
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/GroupsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/GroupsResource.java
new file mode 100755
index 0000000..6e3709d
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/GroupsResource.java
@@ -0,0 +1,120 @@
+package org.keycloak.services.resources.admin;
+
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.annotations.cache.NoCache;
+import org.jboss.resteasy.spi.NotFoundException;
+import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.representations.idm.GroupRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Bill Burke
+ */
+public class GroupsResource {
+
+    private static Logger logger = Logger.getLogger(GroupsResource.class);
+
+    private final RealmModel realm;
+    private final KeycloakSession session;
+    private final RealmAuth auth;
+    private final AdminEventBuilder adminEvent;
+
+    public GroupsResource(RealmModel realm, KeycloakSession session, RealmAuth auth, AdminEventBuilder adminEvent) {
+        this.realm = realm;
+        this.session = session;
+        this.auth = auth;
+        this.adminEvent = adminEvent;
+    }
+
+    @Context private UriInfo uriInfo;
+
+    public GroupsResource(RealmAuth auth, RealmModel realm, KeycloakSession session, AdminEventBuilder adminEvent) {
+        this.realm = realm;
+        this.session = session;
+        this.auth = auth;
+        this.adminEvent = adminEvent;
+    }
+
+    /**
+     * Get group hierarchy.  Only name and ids are returned.
+     *
+     * @return
+     */
+    @GET
+    @NoCache
+    @Produces(MediaType.APPLICATION_JSON)
+    public List<GroupRepresentation> getGroups() {
+        this.auth.requireView();
+        return ModelToRepresentation.toGroupHierarchy(realm, false);
+    }
+
+    /**
+     * Does not expand hierarchy.  Subgroups will not be set.
+     *
+     * @param id
+     * @return
+     */
+    @Path("{id}")
+    public GroupResource getGroupById(@PathParam("id") String id) {
+        GroupModel group = realm.getGroupById(id);
+        if (group == null) {
+            throw new NotFoundException("Could not find group by id");
+        }
+
+        GroupResource resource =  new GroupResource(realm, group, session, this.auth, adminEvent);
+        ResteasyProviderFactory.getInstance().injectProperties(resource);
+        return resource;
+    }
+
+    /**
+     * create or add a top level realm groupSet or create child.  This will update the group and set the parent if it exists.  Create it and set the parent
+     * if the group doesn't exist.
+     *
+     * @param rep
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response addTopLevelGroup(GroupRepresentation rep) {
+        GroupModel child = null;
+        Response.ResponseBuilder builder = Response.status(204);
+        if (rep.getId() != null) {
+            child = realm.getGroupById(rep.getId());
+            if (child == null) {
+                throw new NotFoundException("Could not find child by id");
+            }
+        } else {
+            child = realm.createGroup(rep.getName());
+            GroupResource.updateGroup(rep, child);
+            URI uri = uriInfo.getAbsolutePathBuilder()
+                    .path(child.getId()).build();
+            builder.status(201).location(uri);
+        }
+        realm.moveGroup(child, null);
+        return builder.build();
+    }
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java
index bf17f4b..b49cf91 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java
@@ -79,7 +79,7 @@ public class IdentityProviderResource {
     @Produces(MediaType.APPLICATION_JSON)
     public IdentityProviderRepresentation getIdentityProvider() {
         this.auth.requireView();
-        IdentityProviderRepresentation rep = ModelToRepresentation.toRepresentation(this.identityProviderModel);
+        IdentityProviderRepresentation rep = ModelToRepresentation.toRepresentation(realm, this.identityProviderModel);
         return rep;
     }
 
@@ -117,7 +117,7 @@ public class IdentityProviderResource {
             String newProviderId = providerRep.getAlias();
             String oldProviderId = getProviderIdByInternalId(this.realm, internalId);
 
-            this.realm.updateIdentityProvider(RepresentationToModel.toModel(providerRep));
+            this.realm.updateIdentityProvider(RepresentationToModel.toModel(realm, providerRep));
 
             if (oldProviderId != null && !oldProviderId.equals(newProviderId)) {
 
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java
index d3dc33a..bf452ff 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java
@@ -145,7 +145,7 @@ public class IdentityProvidersResource {
         List<IdentityProviderRepresentation> representations = new ArrayList<IdentityProviderRepresentation>();
 
         for (IdentityProviderModel identityProviderModel : realm.getIdentityProviders()) {
-            representations.add(ModelToRepresentation.toRepresentation(identityProviderModel));
+            representations.add(ModelToRepresentation.toRepresentation(realm, identityProviderModel));
         }
         return representations;
     }
@@ -164,7 +164,7 @@ public class IdentityProvidersResource {
         this.auth.requireManage();
 
         try {
-            IdentityProviderModel identityProvider = RepresentationToModel.toModel(representation);
+            IdentityProviderModel identityProvider = RepresentationToModel.toModel(realm, representation);
             this.realm.addIdentityProvider(identityProvider);
 
             adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, identityProvider.getInternalId())
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java
index 4ef2a63..660b1c4 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java
@@ -1,5 +1,17 @@
 package org.keycloak.services.resources.admin.info;
 
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.core.Context;
+
 import org.keycloak.broker.provider.IdentityProvider;
 import org.keycloak.broker.provider.IdentityProviderFactory;
 import org.keycloak.events.EventType;
@@ -21,10 +33,6 @@ import org.keycloak.representations.idm.ProtocolMapperRepresentation;
 import org.keycloak.representations.idm.ProtocolMapperTypeRepresentation;
 import org.keycloak.social.SocialIdentityProvider;
 
-import javax.ws.rs.GET;
-import javax.ws.rs.core.Context;
-import java.util.*;
-
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
@@ -73,7 +81,6 @@ public class ServerInfoAdminResource {
         for (Spi spi : spis) {
             SpiInfoRepresentation spiRep = new SpiInfoRepresentation();
             spiRep.setInternal(spi.isInternal());
-            spiRep.setSystemInfo(ServerInfoAwareProviderFactory.class.isAssignableFrom(spi.getProviderFactoryClass()));
 
             List<String> providerIds = new LinkedList<>(session.listProviderIds(spi.getProviderClass()));
             Collections.sort(providerIds);
@@ -83,8 +90,9 @@ public class ServerInfoAdminResource {
             if (providerIds != null) {
                 for (String name : providerIds) {
                     ProviderRepresentation provider = new ProviderRepresentation();
-                    if (spiRep.isSystemInfo()) {
-                        provider.setOperationalInfo(((ServerInfoAwareProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(spi.getProviderClass(), name)).getOperationalInfo());
+                    ProviderFactory<?> pi = session.getKeycloakSessionFactory().getProviderFactory(spi.getProviderClass(), name);
+                    if (ServerInfoAwareProviderFactory.class.isAssignableFrom(pi.getClass())) {
+                        provider.setOperationalInfo(((ServerInfoAwareProviderFactory) pi).getOperationalInfo());
                     }
                     providers.put(name, provider);
                 }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/info/SpiInfoRepresentation.java b/services/src/main/java/org/keycloak/services/resources/admin/info/SpiInfoRepresentation.java
index 3f94a5a..1277f8c 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/info/SpiInfoRepresentation.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/info/SpiInfoRepresentation.java
@@ -8,7 +8,6 @@ import java.util.Map;
 public class SpiInfoRepresentation {
 
     private boolean internal;
-    private boolean systemInfo;
 
     private Map<String, ProviderRepresentation> providers;
 
@@ -20,14 +19,6 @@ public class SpiInfoRepresentation {
         this.internal = internal;
     }
 
-    public boolean isSystemInfo() {
-        return systemInfo;
-    }
-
-    public void setSystemInfo(boolean systemInfo) {
-        this.systemInfo = systemInfo;
-    }
-
     public Map<String, ProviderRepresentation> getProviders() {
         return providers;
     }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index 5dd46d0..90eab4a 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -16,6 +16,7 @@ import org.keycloak.events.admin.OperationType;
 import org.keycloak.exportimport.ClientDescriptionConverter;
 import org.keycloak.exportimport.ClientDescriptionConverterFactory;
 import org.keycloak.models.ClientModel;
+import org.keycloak.models.GroupModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ModelDuplicateException;
 import org.keycloak.models.RealmModel;
@@ -23,12 +24,14 @@ import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.cache.CacheRealmProvider;
 import org.keycloak.models.cache.CacheUserProvider;
+import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.models.utils.RepresentationToModel;
 import org.keycloak.protocol.oidc.TokenManager;
 import org.keycloak.provider.ProviderFactory;
 import org.keycloak.representations.adapters.action.GlobalRequestResult;
 import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.GroupRepresentation;
 import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.services.managers.AuthenticationManager;
@@ -141,6 +144,18 @@ public class RealmAdminResource {
     }
 
     /**
+     * Base path for managing client initial access tokens
+     *
+     * @return
+     */
+    @Path("clients-initial-access")
+    public ClientInitialAccessResource getClientInitialAccess() {
+        ClientInitialAccessResource resource = new ClientInitialAccessResource(realm, auth, adminEvent);
+        ResteasyProviderFactory.getInstance().injectProperties(resource);
+        return resource;
+    }
+
+    /**
      * base path for managing realm-level roles of this realm
      *
      * @return
@@ -221,7 +236,7 @@ public class RealmAdminResource {
         } catch (ModelDuplicateException e) {
             throw e;
         } catch (Exception e) {
-            logger.error(e);
+            logger.error(e.getMessage(), e);
             return ErrorResponse.error("Failed to update " + rep.getRealm() + " Realm.", Response.Status.INTERNAL_SERVER_ERROR);
         }
     }
@@ -619,4 +634,69 @@ public class RealmAdminResource {
         return new IdentityProvidersResource(realm, session, this.auth, adminEvent);
     }
 
+    /**
+     * Get group hierarchy.  Only name and ids are returned.
+     *
+     * @return
+     */
+    @GET
+    @NoCache
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("default-groups")
+    public List<GroupRepresentation> getDefaultGroups() {
+        this.auth.requireView();
+        List<GroupRepresentation> defaults = new LinkedList<>();
+        for (GroupModel group : realm.getDefaultGroups()) {
+            defaults.add(ModelToRepresentation.toRepresentation(group, false));
+        }
+        return defaults;
+    }
+    @PUT
+    @NoCache
+    @Path("default-groups/{groupId}")
+    public void addDefaultGroup(@PathParam("groupId") String groupId) {
+        this.auth.requireManage();
+        GroupModel group = realm.getGroupById(groupId);
+        if (group == null) {
+            throw new NotFoundException("Group not found");
+        }
+        realm.addDefaultGroup(group);
+    }
+
+    @DELETE
+    @NoCache
+    @Path("default-groups/{groupId}")
+    public void removeDefaultGroup(@PathParam("groupId") String groupId) {
+        this.auth.requireManage();
+        GroupModel group = realm.getGroupById(groupId);
+        if (group == null) {
+            throw new NotFoundException("Group not found");
+        }
+        realm.removeDefaultGroup(group);
+    }
+
+
+    @Path("groups")
+    public GroupsResource getGroups() {
+        GroupsResource resource =  new GroupsResource(realm, session, this.auth, adminEvent);
+        ResteasyProviderFactory.getInstance().injectProperties(resource);
+        return resource;
+    }
+
+
+    @GET
+    @Path("group-by-path/{path: .*}")
+    @NoCache
+    @Produces(MediaType.APPLICATION_JSON)
+    public GroupRepresentation getGroupByPath(@PathParam("path") String path) {
+        this.auth.requireView();
+        GroupModel found = KeycloakModelUtils.findGroupByPath(realm, path);
+        if (found == null) {
+            throw new NotFoundException("Group path does not exist");
+
+        }
+        return ModelToRepresentation.toGroupHierarchy(found, true);
+    }
+
+
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleMapperResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleMapperResource.java
new file mode 100755
index 0000000..4423c54
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleMapperResource.java
@@ -0,0 +1,271 @@
+package org.keycloak.services.resources.admin;
+
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.annotations.cache.NoCache;
+import org.jboss.resteasy.spi.BadRequestException;
+import org.jboss.resteasy.spi.NotFoundException;
+import org.keycloak.common.ClientConnection;
+import org.keycloak.email.EmailException;
+import org.keycloak.email.EmailProvider;
+import org.keycloak.events.admin.OperationType;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.Constants;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelReadOnlyException;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleMapperModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.models.utils.RepresentationToModel;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.protocol.oidc.TokenManager;
+import org.keycloak.protocol.oidc.utils.RedirectUtils;
+import org.keycloak.representations.idm.ClientMappingsRepresentation;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.representations.idm.MappingsRepresentation;
+import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.services.ErrorResponse;
+import org.keycloak.services.Urls;
+import org.keycloak.services.managers.BruteForceProtector;
+import org.keycloak.services.managers.ClientSessionCode;
+import org.keycloak.services.managers.RealmManager;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Base resource for managing users
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RoleMapperResource {
+    protected static final Logger logger = Logger.getLogger(RoleMapperResource.class);
+
+    protected RealmModel realm;
+
+    private RealmAuth auth;
+
+    private RoleMapperModel roleMapper;
+
+    private AdminEventBuilder adminEvent;
+
+    @Context
+    protected ClientConnection clientConnection;
+
+    @Context
+    protected UriInfo uriInfo;
+
+    @Context
+    protected KeycloakSession session;
+
+    @Context
+    protected HttpHeaders headers;
+
+    @Context
+    protected BruteForceProtector protector;
+
+    public RoleMapperResource(RealmModel realm, RealmAuth auth,  RoleMapperModel roleMapper, AdminEventBuilder adminEvent) {
+        this.auth = auth;
+        this.realm = realm;
+        this.adminEvent = adminEvent;
+        this.roleMapper = roleMapper;
+
+    }
+
+
+    /**
+     * Get role mappings
+     *
+     * @return
+     */
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @NoCache
+    public MappingsRepresentation getRoleMappings() {
+        auth.requireView();
+
+        MappingsRepresentation all = new MappingsRepresentation();
+        Set<RoleModel> realmMappings = roleMapper.getRoleMappings();
+        RealmManager manager = new RealmManager(session);
+        if (realmMappings.size() > 0) {
+            List<RoleRepresentation> realmRep = new ArrayList<RoleRepresentation>();
+            for (RoleModel roleModel : realmMappings) {
+                realmRep.add(ModelToRepresentation.toRepresentation(roleModel));
+            }
+            all.setRealmMappings(realmRep);
+        }
+
+        List<ClientModel> clients = realm.getClients();
+        if (clients.size() > 0) {
+            Map<String, ClientMappingsRepresentation> appMappings = new HashMap<String, ClientMappingsRepresentation>();
+            for (ClientModel client : clients) {
+                Set<RoleModel> roleMappings = roleMapper.getClientRoleMappings(client);
+                if (roleMappings.size() > 0) {
+                    ClientMappingsRepresentation mappings = new ClientMappingsRepresentation();
+                    mappings.setId(client.getId());
+                    mappings.setClient(client.getClientId());
+                    List<RoleRepresentation> roles = new ArrayList<RoleRepresentation>();
+                    mappings.setMappings(roles);
+                    for (RoleModel role : roleMappings) {
+                        roles.add(ModelToRepresentation.toRepresentation(role));
+                    }
+                    appMappings.put(client.getClientId(), mappings);
+                    all.setClientMappings(appMappings);
+                }
+            }
+        }
+        return all;
+    }
+
+    /**
+     * Get realm-level role mappings
+     *
+     * @return
+     */
+    @Path("realm")
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @NoCache
+    public List<RoleRepresentation> getRealmRoleMappings() {
+        auth.requireView();
+
+        Set<RoleModel> realmMappings = roleMapper.getRealmRoleMappings();
+        List<RoleRepresentation> realmMappingsRep = new ArrayList<RoleRepresentation>();
+        for (RoleModel roleModel : realmMappings) {
+            realmMappingsRep.add(ModelToRepresentation.toRepresentation(roleModel));
+        }
+        return realmMappingsRep;
+    }
+
+    /**
+     * Get effective realm-level role mappings
+     *
+     * This will recurse all composite roles to get the result.
+     *
+     * @return
+     */
+    @Path("realm/composite")
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @NoCache
+    public List<RoleRepresentation> getCompositeRealmRoleMappings() {
+        auth.requireView();
+
+        Set<RoleModel> roles = realm.getRoles();
+        List<RoleRepresentation> realmMappingsRep = new ArrayList<RoleRepresentation>();
+        for (RoleModel roleModel : roles) {
+            if (roleMapper.hasRole(roleModel)) {
+               realmMappingsRep.add(ModelToRepresentation.toRepresentation(roleModel));
+            }
+        }
+        return realmMappingsRep;
+    }
+
+    /**
+     * Get realm-level roles that can be mapped
+     *
+     * @return
+     */
+    @Path("realm/available")
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @NoCache
+    public List<RoleRepresentation> getAvailableRealmRoleMappings() {
+        auth.requireView();
+
+        Set<RoleModel> available = realm.getRoles();
+        return ClientRoleMappingsResource.getAvailableRoles(roleMapper, available);
+    }
+
+    /**
+     * Add realm-level role mappings to the user
+     *
+     * @param roles Roles to add
+     */
+    @Path("realm")
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void addRealmRoleMappings(List<RoleRepresentation> roles) {
+        auth.requireManage();
+
+        logger.debugv("** addRealmRoleMappings: {0}", roles);
+
+        for (RoleRepresentation role : roles) {
+            RoleModel roleModel = realm.getRole(role.getName());
+            if (roleModel == null || !roleModel.getId().equals(role.getId())) {
+                throw new NotFoundException("Role not found");
+            }
+            roleMapper.grantRole(roleModel);
+            adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, role.getId()).representation(roles).success();
+        }
+    }
+
+    /**
+     * Delete realm-level role mappings
+     *
+     * @param roles
+     */
+    @Path("realm")
+    @DELETE
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void deleteRealmRoleMappings(List<RoleRepresentation> roles) {
+        auth.requireManage();
+
+        logger.debug("deleteRealmRoleMappings");
+        if (roles == null) {
+            Set<RoleModel> roleModels = roleMapper.getRealmRoleMappings();
+            for (RoleModel roleModel : roleModels) {
+                roleMapper.deleteRoleMapping(roleModel);
+            }
+            adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo).representation(roles).success();
+        } else {
+            for (RoleRepresentation role : roles) {
+                RoleModel roleModel = realm.getRole(role.getName());
+                if (roleModel == null || !roleModel.getId().equals(role.getId())) {
+                    throw new NotFoundException("Role not found");
+                }
+                roleMapper.deleteRoleMapping(roleModel);
+
+                adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo, role.getId()).representation(roles).success();
+            }
+        }
+
+    }
+
+    @Path("clients/{client}")
+    public ClientRoleMappingsResource getUserClientRoleMappingsResource(@PathParam("client") String client) {
+        ClientModel clientModel = realm.getClientById(client);
+        if (clientModel == null) {
+            throw new NotFoundException("Client not found");
+        }
+
+        return new ClientRoleMappingsResource(uriInfo, realm, auth, roleMapper, clientModel, adminEvent);
+
+    }
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index 5e31067..a85c9d2 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -4,6 +4,7 @@ import org.jboss.logging.Logger;
 import org.jboss.resteasy.annotations.cache.NoCache;
 import org.jboss.resteasy.spi.BadRequestException;
 import org.jboss.resteasy.spi.NotFoundException;
+import org.jboss.resteasy.spi.ResteasyProviderFactory;
 import org.keycloak.common.ClientConnection;
 import org.keycloak.authentication.RequiredActionProvider;
 import org.keycloak.email.EmailException;
@@ -16,6 +17,7 @@ import org.keycloak.models.ClientModel;
 import org.keycloak.models.ClientSessionModel;
 import org.keycloak.models.Constants;
 import org.keycloak.models.FederatedIdentityModel;
+import org.keycloak.models.GroupModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ModelDuplicateException;
@@ -35,6 +37,7 @@ import org.keycloak.provider.ProviderFactory;
 import org.keycloak.representations.idm.ClientMappingsRepresentation;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.representations.idm.FederatedIdentityRepresentation;
+import org.keycloak.representations.idm.GroupRepresentation;
 import org.keycloak.representations.idm.MappingsRepresentation;
 import org.keycloak.representations.idm.RoleRepresentation;
 import org.keycloak.representations.idm.UserConsentRepresentation;
@@ -137,7 +140,7 @@ public class UsersResource {
                 throw new NotFoundException("User not found");
             }
 
-            Set<String> attrsToRemove;
+             Set<String> attrsToRemove;
             if (rep.getAttributes() != null) {
                 attrsToRemove = new HashSet<>(user.getAttributes().keySet());
                 attrsToRemove.removeAll(rep.getAttributes().keySet());
@@ -660,218 +663,22 @@ public class UsersResource {
         return results;
     }
 
-    /**
-     * Get role mappings for the user
-     *
-     * @param id User id
-     * @return
-     */
     @Path("{id}/role-mappings")
-    @GET
-    @Produces(MediaType.APPLICATION_JSON)
-    @NoCache
-    public MappingsRepresentation getRoleMappings(@PathParam("id") String id) {
-        auth.requireView();
-
-        UserModel user = session.users().getUserById(id, realm);
-        if (user == null) {
-            throw new NotFoundException("User not found");
-        }
-
-        MappingsRepresentation all = new MappingsRepresentation();
-        Set<RoleModel> realmMappings = user.getRoleMappings();
-        RealmManager manager = new RealmManager(session);
-        if (realmMappings.size() > 0) {
-            List<RoleRepresentation> realmRep = new ArrayList<RoleRepresentation>();
-            for (RoleModel roleModel : realmMappings) {
-                realmRep.add(ModelToRepresentation.toRepresentation(roleModel));
-            }
-            all.setRealmMappings(realmRep);
-        }
-
-        List<ClientModel> clients = realm.getClients();
-        if (clients.size() > 0) {
-            Map<String, ClientMappingsRepresentation> appMappings = new HashMap<String, ClientMappingsRepresentation>();
-            for (ClientModel client : clients) {
-                Set<RoleModel> roleMappings = user.getClientRoleMappings(client);
-                if (roleMappings.size() > 0) {
-                    ClientMappingsRepresentation mappings = new ClientMappingsRepresentation();
-                    mappings.setId(client.getId());
-                    mappings.setClient(client.getClientId());
-                    List<RoleRepresentation> roles = new ArrayList<RoleRepresentation>();
-                    mappings.setMappings(roles);
-                    for (RoleModel role : roleMappings) {
-                        roles.add(ModelToRepresentation.toRepresentation(role));
-                    }
-                    appMappings.put(client.getClientId(), mappings);
-                    all.setClientMappings(appMappings);
-                }
-            }
-        }
-        return all;
-    }
-
-    /**
-     * Get realm-level role mappings for the user
-     *
-     * @param id User id
-     * @return
-     */
-    @Path("{id}/role-mappings/realm")
-    @GET
-    @Produces(MediaType.APPLICATION_JSON)
-    @NoCache
-    public List<RoleRepresentation> getRealmRoleMappings(@PathParam("id") String id) {
-        auth.requireView();
-
-        UserModel user = session.users().getUserById(id, realm);
-        if (user == null) {
-            throw new NotFoundException("User not found");
-        }
-
-        Set<RoleModel> realmMappings = user.getRealmRoleMappings();
-        List<RoleRepresentation> realmMappingsRep = new ArrayList<RoleRepresentation>();
-        for (RoleModel roleModel : realmMappings) {
-            realmMappingsRep.add(ModelToRepresentation.toRepresentation(roleModel));
-        }
-        return realmMappingsRep;
-    }
-
-    /**
-     * Get effective realm-level role mappings for the user
-     *
-     * This will recurse all composite roles to get the result.
-     *
-     * @param id User id
-     * @return
-     */
-    @Path("{id}/role-mappings/realm/composite")
-    @GET
-    @Produces(MediaType.APPLICATION_JSON)
-    @NoCache
-    public List<RoleRepresentation> getCompositeRealmRoleMappings(@PathParam("id") String id) {
-        auth.requireView();
-
-        UserModel user = session.users().getUserById(id, realm);
-        if (user == null) {
-            throw new NotFoundException("User not found");
-        }
-
-        Set<RoleModel> roles = realm.getRoles();
-        List<RoleRepresentation> realmMappingsRep = new ArrayList<RoleRepresentation>();
-        for (RoleModel roleModel : roles) {
-            if (user.hasRole(roleModel)) {
-               realmMappingsRep.add(ModelToRepresentation.toRepresentation(roleModel));
-            }
-        }
-        return realmMappingsRep;
-    }
-
-    /**
-     * Get realm-level roles that can be mapped to this user
-     *
-     * @param id User id
-     * @return
-     */
-    @Path("{id}/role-mappings/realm/available")
-    @GET
-    @Produces(MediaType.APPLICATION_JSON)
-    @NoCache
-    public List<RoleRepresentation> getAvailableRealmRoleMappings(@PathParam("id") String id) {
-        auth.requireView();
-
-        UserModel user = session.users().getUserById(id, realm);
-        if (user == null) {
-            throw new NotFoundException("User not found");
-        }
-
-        Set<RoleModel> available = realm.getRoles();
-        return UserClientRoleMappingsResource.getAvailableRoles(user, available);
-    }
-
-    /**
-     * Add realm-level role mappings to the user
-     *
-     * @param id User id
-     * @param roles Roles to add
-     */
-    @Path("{id}/role-mappings/realm")
-    @POST
-    @Consumes(MediaType.APPLICATION_JSON)
-    public void addRealmRoleMappings(@PathParam("id") String id, List<RoleRepresentation> roles) {
-        auth.requireManage();
+    public RoleMapperResource getRoleMappings(@PathParam("id") String id) {
 
-        logger.debugv("** addRealmRoleMappings: {0}", roles);
         UserModel user = session.users().getUserById(id, realm);
         if (user == null) {
             throw new NotFoundException("User not found");
         }
+        auth.init(RealmAuth.Resource.USER);
 
-        for (RoleRepresentation role : roles) {
-            RoleModel roleModel = realm.getRole(role.getName());
-            if (roleModel == null || !roleModel.getId().equals(role.getId())) {
-                throw new NotFoundException("Role not found");
-            }
-            user.grantRole(roleModel);
-            adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, role.getId()).representation(roles).success();
-        }
-    }
-
-    /**
-     * Delete realm-level role mappings
-     *
-     * @param id User id
-     * @param roles
-     */
-    @Path("{id}/role-mappings/realm")
-    @DELETE
-    @Consumes(MediaType.APPLICATION_JSON)
-    public void deleteRealmRoleMappings(@PathParam("id") String id, List<RoleRepresentation> roles) {
-        auth.requireManage();
-
-        logger.debug("deleteRealmRoleMappings");
-        UserModel user = session.users().getUserById(id, realm);
-        if (user == null) {
-            throw new NotFoundException("User not found");
-        }
-
-        if (roles == null) {
-            Set<RoleModel> roleModels = user.getRealmRoleMappings();
-            for (RoleModel roleModel : roleModels) {
-                user.deleteRoleMapping(roleModel);
-            }
-            adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo).representation(roles).success();
-        } else {
-            for (RoleRepresentation role : roles) {
-                RoleModel roleModel = realm.getRole(role.getName());
-                if (roleModel == null || !roleModel.getId().equals(role.getId())) {
-                    throw new NotFoundException("Role not found");
-                }
-                user.deleteRoleMapping(roleModel);
-
-                adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo, role.getId()).representation(roles).success();
-            }
-        }
-
-    }
-
-    @Path("{id}/role-mappings/clients/{client}")
-    public UserClientRoleMappingsResource getUserClientRoleMappingsResource(@PathParam("id") String id, @PathParam("client") String client) {
-        UserModel user = session.users().getUserById(id, realm);
-        if (user == null) {
-            throw new NotFoundException("User not found");
-        }
-
-        ClientModel clientModel = realm.getClientById(client);
-        if (clientModel == null) {
-            throw new NotFoundException("Client not found");
-        }
-
-        return new UserClientRoleMappingsResource(uriInfo, realm, auth, user, clientModel, adminEvent);
+        RoleMapperResource resource =  new RoleMapperResource(realm, auth, user, adminEvent);
+        ResteasyProviderFactory.getInstance().injectProperties(resource);
+        return resource;
 
     }
 
-    /**
+     /**
      * Set up a temporary password for the user
      *
      * User will have to reset the temporary password next time they log in.
@@ -1106,4 +913,57 @@ public class UsersResource {
         return clientSession;
     }
 
+    @GET
+    @Path("{id}/groups")
+    @NoCache
+    @Produces(MediaType.APPLICATION_JSON)
+    public List<GroupRepresentation> groupMembership(@PathParam("id") String id) {
+        auth.requireView();
+
+        UserModel user = session.users().getUserById(id, realm);
+        if (user == null) {
+            throw new NotFoundException("User not found");
+        }
+        List<GroupRepresentation> memberships = new LinkedList<>();
+        for (GroupModel group : user.getGroups()) {
+            memberships.add(ModelToRepresentation.toRepresentation(group, false));
+        }
+        return memberships;
+    }
+
+    @DELETE
+    @Path("{id}/groups/{groupId}")
+    @NoCache
+    public void removeMembership(@PathParam("id") String id, @PathParam("groupId") String groupId) {
+        auth.requireManage();
+
+        UserModel user = session.users().getUserById(id, realm);
+        if (user == null) {
+            throw new NotFoundException("User not found");
+        }
+        GroupModel group = session.realms().getGroupById(groupId, realm);
+        if (group == null) {
+            throw new NotFoundException("Group not found");
+        }
+        if (user.isMemberOf(group)) user.leaveGroup(group);
+    }
+
+    @PUT
+    @Path("{id}/groups/{groupId}")
+    @NoCache
+    public void joinGroup(@PathParam("id") String id, @PathParam("groupId") String groupId) {
+        auth.requireManage();
+
+        UserModel user = session.users().getUserById(id, realm);
+        if (user == null) {
+            throw new NotFoundException("User not found");
+        }
+        GroupModel group = session.realms().getGroupById(groupId, realm);
+        if (group == null) {
+            throw new NotFoundException("Group not found");
+        }
+        if (!user.isMemberOf(group)) user.joinGroup(group);
+    }
+
+
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/AttributeFormDataProcessor.java b/services/src/main/java/org/keycloak/services/resources/AttributeFormDataProcessor.java
index c9ec4c3..9fb60ec 100755
--- a/services/src/main/java/org/keycloak/services/resources/AttributeFormDataProcessor.java
+++ b/services/src/main/java/org/keycloak/services/resources/AttributeFormDataProcessor.java
@@ -3,6 +3,8 @@ package org.keycloak.services.resources;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.keycloak.authentication.requiredactions.util.UpdateProfileContext;
+import org.keycloak.authentication.requiredactions.util.UserUpdateProfileContext;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 
@@ -21,6 +23,11 @@ public class AttributeFormDataProcessor {
      * @param user
      */
     public static void process(MultivaluedMap<String, String> formData, RealmModel realm, UserModel user) {
+        UpdateProfileContext userCtx = new UserUpdateProfileContext(realm, user);
+        process(formData, realm, userCtx);
+    }
+
+    public static void process(MultivaluedMap<String, String> formData, RealmModel realm, UpdateProfileContext user) {
         for (String key : formData.keySet()) {
             if (!key.startsWith("user.attributes.")) continue;
             String attribute = key.substring("user.attributes.".length());
@@ -36,7 +43,6 @@ public class AttributeFormDataProcessor {
 
             user.setAttribute(attribute, modelValue);
         }
-
     }
 
     private static void addOrSetValue(List<String> list, int index, String value) {
diff --git a/services/src/main/java/org/keycloak/services/resources/ClientsManagementService.java b/services/src/main/java/org/keycloak/services/resources/ClientsManagementService.java
index ddb301d..a933712 100755
--- a/services/src/main/java/org/keycloak/services/resources/ClientsManagementService.java
+++ b/services/src/main/java/org/keycloak/services/resources/ClientsManagementService.java
@@ -153,7 +153,7 @@ public class ClientsManagementService {
     }
 
     protected ClientModel authorizeClient() {
-        ClientModel client = AuthorizeClientUtil.authorizeClient(session, event, realm).getClient();
+        ClientModel client = AuthorizeClientUtil.authorizeClient(session, event).getClient();
 
         if (client.isPublicClient()) {
             Map<String, String> error = new HashMap<String, String>();
diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
index a171bdb..5912536 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -20,6 +20,9 @@ package org.keycloak.services.resources;
 import org.jboss.logging.Logger;
 import org.jboss.resteasy.spi.HttpRequest;
 import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator;
+import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
 import org.keycloak.common.ClientConnection;
 import org.keycloak.authentication.AuthenticationProcessor;
 import org.keycloak.broker.provider.AuthenticationRequest;
@@ -28,10 +31,12 @@ import org.keycloak.broker.provider.IdentityBrokerException;
 import org.keycloak.broker.provider.IdentityProvider;
 import org.keycloak.broker.provider.IdentityProviderFactory;
 import org.keycloak.broker.provider.IdentityProviderMapper;
+import org.keycloak.common.util.Time;
 import org.keycloak.events.Details;
 import org.keycloak.events.Errors;
 import org.keycloak.events.EventBuilder;
 import org.keycloak.events.EventType;
+import org.keycloak.login.LoginFormsProvider;
 import org.keycloak.models.AuthenticationFlowModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.ClientSessionModel;
@@ -49,7 +54,6 @@ import org.keycloak.models.utils.FormMessage;
 import org.keycloak.protocol.oidc.TokenManager;
 import org.keycloak.provider.ProviderFactory;
 import org.keycloak.representations.AccessToken;
-import org.keycloak.representations.idm.IdentityProviderRepresentation;
 import org.keycloak.services.managers.AppAuthManager;
 import org.keycloak.services.managers.AuthenticationManager.AuthResult;
 import org.keycloak.services.managers.BruteForceProtector;
@@ -61,6 +65,7 @@ import org.keycloak.services.Urls;
 import org.keycloak.services.validation.Validation;
 import org.keycloak.social.SocialIdentityProvider;
 import org.keycloak.common.util.ObjectUtil;
+import org.keycloak.util.JsonSerialization;
 
 import javax.ws.rs.*;
 import javax.ws.rs.core.Context;
@@ -70,6 +75,8 @@ import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
 
+import java.io.IOException;
+import java.net.URI;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -79,7 +86,6 @@ import java.util.Set;
 import static org.keycloak.models.AccountRoles.MANAGE_ACCOUNT;
 import static org.keycloak.models.ClientSessionModel.Action.AUTHENTICATE;
 import static org.keycloak.models.Constants.ACCOUNT_MANAGEMENT_CLIENT_ID;
-import static org.keycloak.models.UserModel.RequiredAction.UPDATE_PROFILE;
 
 /**
  * <p></p>
@@ -285,27 +291,141 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
         }
 
         if (federatedUser == null) {
-            try {
-                federatedUser = createUser(context);
 
-                if (IdentityProviderRepresentation.UPFLM_ON.equals(identityProviderConfig.getUpdateProfileFirstLoginMode()) 
-                        || (IdentityProviderRepresentation.UPFLM_MISSING.equals(identityProviderConfig.getUpdateProfileFirstLoginMode()) && !Validation.validateUserMandatoryFields(realmModel, federatedUser))) {
-                    if (isDebugEnabled()) {
-                        LOGGER.debugf("Identity provider requires update profile action.", federatedUser);
-                    }
-                    federatedUser.addRequiredAction(UPDATE_PROFILE);
-                }
-                if(identityProviderConfig.isTrustEmail() && !Validation.isBlank(federatedUser.getEmail())){
-                    federatedUser.setEmailVerified(true);
+            LOGGER.debugf("Federated user not found for provider '%s' and broker username '%s' . Redirecting to flow for firstBrokerLogin", providerId, context.getUsername());
+
+            String username = context.getModelUsername();
+            if (username == null) {
+                if (this.realmModel.isRegistrationEmailAsUsername() && !Validation.isBlank(context.getEmail())) {
+                    username = context.getEmail();
+                } else if (context.getUsername() == null) {
+                    username = context.getIdpConfig().getAlias() + "." + context.getId();
+                } else {
+                    username = context.getIdpConfig().getAlias() + "." + context.getUsername();
                 }
-            } catch (Exception e) {
-                return redirectToLoginPage(e, clientCode);
             }
+            username = username.trim();
+            context.setModelUsername(username);
+
+            clientSession.setTimestamp(Time.currentTime());
+
+            SerializedBrokeredIdentityContext ctx = SerializedBrokeredIdentityContext.serialize(context);
+            ctx.saveToClientSession(clientSession);
+
+            URI redirect = LoginActionsService.firstBrokerLoginProcessor(uriInfo)
+                    .queryParam(OAuth2Constants.CODE, context.getCode())
+                    .build(realmModel.getName());
+            return Response.status(302).location(redirect).build();
+
         } else {
             updateFederatedIdentity(context, federatedUser);
+
+            boolean firstBrokerLoginInProgress = (clientSession.getNote(AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE) != null);
+            if (firstBrokerLoginInProgress) {
+                LOGGER.debugf("Reauthenticated with broker '%s' when linking user '%s' with other broker", context.getIdpConfig().getAlias(), federatedUser.getUsername());
+
+                UserModel linkingUser = AbstractIdpAuthenticator.getExistingUser(session, realmModel, clientSession);
+                if (!linkingUser.getId().equals(federatedUser.getId())) {
+                    return redirectToErrorPage(Messages.IDENTITY_PROVIDER_DIFFERENT_USER_MESSAGE, federatedUser.getUsername(), linkingUser.getUsername());
+                }
+
+                clientSession.setAuthenticatedUser(federatedUser);
+                return afterFirstBrokerLogin(context.getCode());
+            }
+
+            return finishBrokerAuthentication(context, federatedUser, clientSession, providerId);
         }
+    }
+
+    // Callback from LoginActionsService after first login with broker was done and Keycloak account is successfully linked/created
+    @GET
+    @Path("/after-first-broker-login")
+    public Response afterFirstBrokerLogin(@QueryParam("code") String code) {
+        ClientSessionCode clientCode = parseClientSessionCode(code);
+        ClientSessionModel clientSession = clientCode.getClientSession();
+
+        try {
+            SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromClientSession(clientSession);
+            if (serializedCtx == null) {
+                throw new IdentityBrokerException("Not found serialized context in clientSession");
+            }
+            BrokeredIdentityContext context = serializedCtx.deserialize(session, clientSession);
+            String providerId = context.getIdpConfig().getAlias();
+
+            // firstBrokerLogin workflow finished. Removing note now
+            clientSession.removeNote(AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE);
+
+            UserModel federatedUser = clientSession.getAuthenticatedUser();
+            if (federatedUser == null) {
+                throw new IdentityBrokerException("Couldn't found authenticated federatedUser in clientSession");
+            }
 
+            if (context.getIdpConfig().isAddReadTokenRoleOnCreate()) {
+                RoleModel readTokenRole = realmModel.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID).getRole(Constants.READ_TOKEN_ROLE);
+                federatedUser.grantRole(readTokenRole);
+            }
 
+            // Add federated identity link here
+            FederatedIdentityModel federatedIdentityModel = new FederatedIdentityModel(context.getIdpConfig().getAlias(), context.getId(),
+                    context.getUsername(), context.getToken());
+            session.users().addFederatedIdentity(realmModel, federatedUser, federatedIdentityModel);
+
+            EventBuilder event = this.event.clone().user(federatedUser)
+                    .detail(Details.CODE_ID, clientSession.getId())
+                    .detail(Details.USERNAME, federatedUser.getUsername())
+                    .detail(Details.IDENTITY_PROVIDER, providerId)
+                    .detail(Details.IDENTITY_PROVIDER_USERNAME, context.getUsername())
+                    .removeDetail("auth_method");
+
+            String isRegisteredNewUser = clientSession.getNote(AbstractIdpAuthenticator.BROKER_REGISTERED_NEW_USER);
+            if (Boolean.parseBoolean(isRegisteredNewUser)) {
+
+                LOGGER.debugf("Registered new user '%s' after first login with identity provider '%s'. Identity provider username is '%s' . ", federatedUser.getUsername(), providerId, context.getUsername());
+
+                context.getIdp().importNewUser(session, realmModel, federatedUser, context);
+                Set<IdentityProviderMapperModel> mappers = realmModel.getIdentityProviderMappersByAlias(providerId);
+                if (mappers != null) {
+                    KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
+                    for (IdentityProviderMapperModel mapper : mappers) {
+                        IdentityProviderMapper target = (IdentityProviderMapper)sessionFactory.getProviderFactory(IdentityProviderMapper.class, mapper.getIdentityProviderMapper());
+                        target.importNewUser(session, realmModel, federatedUser, mapper, context);
+                    }
+                }
+
+                if (context.getIdpConfig().isTrustEmail() && !Validation.isBlank(federatedUser.getEmail()) && !Boolean.parseBoolean(clientSession.getNote(AbstractIdpAuthenticator.UPDATE_PROFILE_EMAIL_CHANGED))) {
+                    LOGGER.debugf("Email verified automatically after registration of user '%s' through Identity provider '%s' ", federatedUser.getUsername(), context.getIdpConfig().getAlias());
+                    federatedUser.setEmailVerified(true);
+                }
+
+                event.event(EventType.REGISTER)
+                        .detail(Details.REGISTER_METHOD, "broker")
+                        .detail(Details.EMAIL, federatedUser.getEmail())
+                        .success();
+
+            } else {
+                LOGGER.debugf("Linked existing keycloak user '%s' with identity provider '%s' . Identity provider username is '%s' .", federatedUser.getUsername(), providerId, context.getUsername());
+
+                event.event(EventType.FEDERATED_IDENTITY_LINK)
+                        .success();
+
+                updateFederatedIdentity(context, federatedUser);
+            }
+
+            String isDifferentBrowser = clientSession.getNote(AbstractIdpAuthenticator.IS_DIFFERENT_BROWSER);
+            if (Boolean.parseBoolean(isDifferentBrowser)) {
+                session.sessions().removeClientSession(realmModel, clientSession);
+                return session.getProvider(LoginFormsProvider.class)
+                            .setSuccess(Messages.IDENTITY_PROVIDER_LINK_SUCCESS, context.getIdpConfig().getAlias(), context.getUsername())
+                            .createInfoPage();
+            } else {
+                return finishBrokerAuthentication(context, federatedUser, clientSession, providerId);
+            }
+        }  catch (Exception e) {
+            return redirectToErrorPage(Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, e);
+        }
+    }
+
+    private Response finishBrokerAuthentication(BrokeredIdentityContext context, UserModel federatedUser, ClientSessionModel clientSession, String providerId) {
         UserSessionModel userSession = this.session.sessions()
                 .createUserSession(this.realmModel, federatedUser, federatedUser.getUsername(), this.clientConnection.getRemoteAddr(), "broker", false, context.getBrokerSessionId(), context.getBrokerUserId());
 
@@ -343,10 +463,10 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
     }
 
     private Response performAccountLinking(ClientSessionModel clientSession, BrokeredIdentityContext context, FederatedIdentityModel federatedIdentityModel, UserModel federatedUser) {
-        this.event.event(EventType.IDENTITY_PROVIDER_ACCCOUNT_LINKING);
+        this.event.event(EventType.FEDERATED_IDENTITY_LINK);
 
         if (federatedUser != null) {
-            return redirectToErrorPage(Messages.IDENTITY_PROVIDER_ALREADY_LINKED, context.getIdpConfig().getAlias());
+            return redirectToAccountErrorPage(clientSession, Messages.IDENTITY_PROVIDER_ALREADY_LINKED, context.getIdpConfig().getAlias());
         }
 
         UserModel authenticatedUser = clientSession.getUserSession().getUser();
@@ -356,19 +476,21 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
         }
 
         if (!authenticatedUser.isEnabled()) {
-            fireErrorEvent(Errors.USER_DISABLED);
-            return redirectToErrorPage(Messages.ACCOUNT_DISABLED);
+            return redirectToAccountErrorPage(clientSession, Messages.ACCOUNT_DISABLED);
         }
 
         if (!authenticatedUser.hasRole(this.realmModel.getClientByClientId(ACCOUNT_MANAGEMENT_CLIENT_ID).getRole(MANAGE_ACCOUNT))) {
-            fireErrorEvent(Errors.NOT_ALLOWED);
             return redirectToErrorPage(Messages.INSUFFICIENT_PERMISSION);
         }
 
         this.session.users().addFederatedIdentity(this.realmModel, authenticatedUser, federatedIdentityModel);
         context.getIdp().attachUserSession(clientSession.getUserSession(), clientSession, context);
 
-        this.event.success();
+        this.event.user(authenticatedUser)
+                .detail(Details.USERNAME, authenticatedUser.getUsername())
+                .detail(Details.IDENTITY_PROVIDER, federatedIdentityModel.getIdentityProvider())
+                .detail(Details.IDENTITY_PROVIDER_USERNAME, federatedIdentityModel.getUserName())
+                .success();
         return Response.status(302).location(UriBuilder.fromUri(clientSession.getRedirectUri()).build()).build();
     }
 
@@ -376,7 +498,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
         FederatedIdentityModel federatedIdentityModel = this.session.users().getFederatedIdentity(federatedUser, context.getIdpConfig().getAlias(), this.realmModel);
 
         // Skip DB write if tokens are null or equal
-        if (context.getIdpConfig().isStoreToken() && !ObjectUtil.isEqualOrNull(context.getToken(), federatedIdentityModel.getToken())) {
+        if (context.getIdpConfig().isStoreToken() && !ObjectUtil.isEqualOrBothNull(context.getToken(), federatedIdentityModel.getToken())) {
             federatedIdentityModel.setToken(context.getToken());
 
             this.session.users().updateFederatedIdentity(this.realmModel, federatedUser, federatedIdentityModel);
@@ -412,6 +534,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
 
                 LOGGER.debugf("Got authorization code from client [%s].", client.getClientId());
                 this.event.client(client);
+                this.session.getContext().setClient(client);
 
                 if (clientSession.getUserSession() != null) {
                     this.event.session(clientSession.getUserSession());
@@ -457,6 +580,20 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
         return ErrorPage.error(this.session, message, parameters);
     }
 
+    private Response redirectToAccountErrorPage(ClientSessionModel clientSession, String message, Object ... parameters) {
+        fireErrorEvent(message);
+
+        FormMessage errorMessage = new FormMessage(message, parameters);
+        try {
+            String serializedError = JsonSerialization.writeValueAsString(errorMessage);
+            clientSession.setNote(AccountService.ACCOUNT_MGMT_FORWARDED_ERROR_NOTE, serializedError);
+        } catch (IOException ioe) {
+            throw new RuntimeException(ioe);
+        }
+
+        return Response.status(302).location(UriBuilder.fromUri(clientSession.getRedirectUri()).build()).build();
+    }
+
     private Response redirectToLoginPage(Throwable t, ClientSessionCode clientCode) {
         String message = t.getMessage();
 
@@ -534,100 +671,11 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
     }
 
     private IdentityProviderModel getIdentityProviderConfig(String providerId) {
-        for (IdentityProviderModel model : this.realmModel.getIdentityProviders()) {
-            if (model.getAlias().equals(providerId)) {
-                return model;
-            }
-        }
-
-        throw new IdentityBrokerException("Configuration for identity provider [" + providerId + "] not found.");
-    }
-
-    private UserModel createUser(BrokeredIdentityContext context) {
-        FederatedIdentityModel federatedIdentityModel = new FederatedIdentityModel(context.getIdpConfig().getAlias(), context.getId(),
-                context.getUsername(), context.getToken());
-        // Check if no user already exists with this username or email
-        UserModel existingUser = null;
-
-        if (context.getEmail() != null) {
-            existingUser = this.session.users().getUserByEmail(context.getEmail(), this.realmModel);
-        }
-
-        if (existingUser != null) {
-            fireErrorEvent(Errors.FEDERATED_IDENTITY_EMAIL_EXISTS);
-            throw new IdentityBrokerException(Messages.FEDERATED_IDENTITY_EMAIL_EXISTS);
-        }
-        String username = context.getModelUsername();
-        if (username == null) {
-            username = context.getUsername();
-            if (this.realmModel.isRegistrationEmailAsUsername() && !Validation.isBlank(context.getEmail())) {
-                username = context.getEmail();
-            } else if (username == null) {
-                username = context.getIdpConfig().getAlias() + "." + context.getId();
-            } else {
-                username = context.getIdpConfig().getAlias() + "." + context.getUsername();
-            }
-        }
-        if (username == null) {
-            LOGGER.warn("Unknown username");
-            fireErrorEvent(Errors.FEDERATED_IDENTITY_USERNAME_EXISTS);
-            throw new IdentityBrokerException(Messages.FEDERATED_IDENTITY_USERNAME_EXISTS);
-
-        }
-        username = username.trim();
-
-        existingUser = this.session.users().getUserByUsername(username, this.realmModel);
-
-        if (existingUser != null) {
-            fireErrorEvent(Errors.FEDERATED_IDENTITY_USERNAME_EXISTS);
-            throw new IdentityBrokerException(Messages.FEDERATED_IDENTITY_USERNAME_EXISTS);
-        }
-
-        if (isDebugEnabled()) {
-            LOGGER.debugf("Creating account from identity [%s].", federatedIdentityModel);
+        IdentityProviderModel model = this.realmModel.getIdentityProviderByAlias(providerId);
+        if (model == null) {
+            throw new IdentityBrokerException("Configuration for identity provider [" + providerId + "] not found.");
         }
-
-        UserModel federatedUser = this.session.users().addUser(this.realmModel, username);
-
-        if (isDebugEnabled()) {
-            LOGGER.debugf("Account [%s] created.", federatedUser);
-        }
-
-        federatedUser.setEnabled(true);
-        federatedUser.setEmail(context.getEmail());
-        federatedUser.setFirstName(context.getFirstName());
-        federatedUser.setLastName(context.getLastName());
-
-
-        if (context.getIdpConfig().isAddReadTokenRoleOnCreate()) {
-            RoleModel readTokenRole = realmModel.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID).getRole(Constants.READ_TOKEN_ROLE);
-            federatedUser.grantRole(readTokenRole);
-        }
-
-        if (context.getIdpConfig().isStoreToken()) {
-            federatedIdentityModel.setToken(context.getToken());
-        }
-
-        this.session.users().addFederatedIdentity(this.realmModel, federatedUser, federatedIdentityModel);
-
-        context.getIdp().importNewUser(session, realmModel, federatedUser, context);
-        Set<IdentityProviderMapperModel> mappers = realmModel.getIdentityProviderMappersByAlias(context.getIdpConfig().getAlias());
-        if (mappers != null) {
-            KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
-            for (IdentityProviderMapperModel mapper : mappers) {
-                IdentityProviderMapper target = (IdentityProviderMapper)sessionFactory.getProviderFactory(IdentityProviderMapper.class, mapper.getIdentityProviderMapper());
-                target.importNewUser(session, realmModel, federatedUser, mapper, context);
-            }
-        }
-
-
-        this.event.clone().user(federatedUser).event(EventType.REGISTER)
-                .detail(Details.IDENTITY_PROVIDER, federatedIdentityModel.getIdentityProvider())
-                .detail(Details.IDENTITY_PROVIDER_USERNAME, context.getUsername())
-                .removeDetail("auth_method")
-                .success();
-
-        return federatedUser;
+        return model;
     }
 
     private Response corsResponse(Response response, ClientModel clientModel) {
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index 20de78b..7f15d2d 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -23,8 +23,10 @@ package org.keycloak.services.resources;
 
 import org.jboss.logging.Logger;
 import org.jboss.resteasy.spi.HttpRequest;
-import org.keycloak.authentication.AuthenticationFlowError;
+import org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator;
 import org.keycloak.authentication.requiredactions.VerifyEmail;
+import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
+import org.keycloak.broker.provider.BrokeredIdentityContext;
 import org.keycloak.common.ClientConnection;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.authentication.AuthenticationProcessor;
@@ -33,6 +35,7 @@ import org.keycloak.authentication.RequiredActionContextResult;
 import org.keycloak.authentication.RequiredActionFactory;
 import org.keycloak.authentication.RequiredActionProvider;
 import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator;
+import org.keycloak.common.util.Time;
 import org.keycloak.events.Details;
 import org.keycloak.events.Errors;
 import org.keycloak.events.EventBuilder;
@@ -51,7 +54,6 @@ import org.keycloak.models.UserModel;
 import org.keycloak.models.UserModel.RequiredAction;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.utils.FormMessage;
-import org.keycloak.models.utils.HmacOTP;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.LoginProtocol;
 import org.keycloak.protocol.RestartLoginCookie;
@@ -92,6 +94,7 @@ public class LoginActionsService {
     public static final String REGISTRATION_PATH = "registration";
     public static final String RESET_CREDENTIALS_PATH = "reset-credentials";
     public static final String REQUIRED_ACTION = "required-action";
+    public static final String FIRST_BROKER_LOGIN_PATH = "first-broker-login";
 
     private RealmModel realm;
 
@@ -134,6 +137,10 @@ public class LoginActionsService {
         return loginActionsBaseUrl(uriInfo).path(LoginActionsService.class, "processRegister");
     }
 
+    public static UriBuilder firstBrokerLoginProcessor(UriInfo uriInfo) {
+        return loginActionsBaseUrl(uriInfo).path(LoginActionsService.class, "firstBrokerLoginGet");
+    }
+
     public static UriBuilder loginActionsBaseUrl(UriBuilder baseUriBuilder) {
         return baseUriBuilder.path(RealmsResource.class).path(RealmsResource.class, "getLoginActionsService");
     }
@@ -208,7 +215,7 @@ public class LoginActionsService {
                         ClientSessionModel clientSession = RestartLoginCookie.restartSession(session, realm, code);
                         if (clientSession != null) {
                             event.clone().detail(Details.RESTART_AFTER_TIMEOUT, "true").error(Errors.EXPIRED_CODE);
-                            response = processFlow(null, clientSession, AUTHENTICATE_PATH, realm.getBrowserFlow(), Messages.LOGIN_TIMEOUT);
+                            response = processFlow(null, clientSession, AUTHENTICATE_PATH, realm.getBrowserFlow(), Messages.LOGIN_TIMEOUT, new AuthenticationProcessor());
                             return false;
                         }
                     } catch (Exception e) {
@@ -267,11 +274,10 @@ public class LoginActionsService {
     }
 
     protected Response processAuthentication(String execution, ClientSessionModel clientSession, String errorMessage) {
-        return processFlow(execution, clientSession, AUTHENTICATE_PATH, realm.getBrowserFlow(), errorMessage);
+        return processFlow(execution, clientSession, AUTHENTICATE_PATH, realm.getBrowserFlow(), errorMessage, new AuthenticationProcessor());
     }
 
-    protected Response processFlow(String execution, ClientSessionModel clientSession, String flowPath, AuthenticationFlowModel flow, String errorMessage) {
-        AuthenticationProcessor processor = new AuthenticationProcessor();
+    protected Response processFlow(String execution, ClientSessionModel clientSession, String flowPath, AuthenticationFlowModel flow, String errorMessage, AuthenticationProcessor processor) {
         processor.setClientSession(clientSession)
                 .setFlowPath(flowPath)
                 .setBrowserFlow(true)
@@ -384,12 +390,33 @@ public class LoginActionsService {
     }
 
     protected Response processResetCredentials(String execution, ClientSessionModel clientSession, String errorMessage) {
-        return processFlow(execution, clientSession, RESET_CREDENTIALS_PATH, realm.getResetCredentialsFlow(), errorMessage);
+        AuthenticationProcessor authProcessor = new AuthenticationProcessor() {
+
+            @Override
+            protected Response authenticationComplete() {
+                boolean firstBrokerLoginInProgress = (clientSession.getNote(AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE) != null);
+                if (firstBrokerLoginInProgress) {
+
+                    UserModel linkingUser = AbstractIdpAuthenticator.getExistingUser(session, realm, clientSession);
+                    if (!linkingUser.getId().equals(clientSession.getAuthenticatedUser().getId())) {
+                        return ErrorPage.error(session, Messages.IDENTITY_PROVIDER_DIFFERENT_USER_MESSAGE, clientSession.getAuthenticatedUser().getUsername(), linkingUser.getUsername());
+                    }
+
+                    logger.debugf("Forget-password flow finished when authenticated user '%s' after first broker login.", linkingUser.getUsername());
+
+                    return redirectToAfterFirstBrokerLoginEndpoint(clientSession);
+                } else {
+                    return super.authenticationComplete();
+                }
+            }
+        };
+
+        return processFlow(execution, clientSession, RESET_CREDENTIALS_PATH, realm.getResetCredentialsFlow(), errorMessage, authProcessor);
     }
 
 
     protected Response processRegistration(String execution, ClientSessionModel clientSession, String errorMessage) {
-        return processFlow(execution, clientSession, REGISTRATION_PATH, realm.getRegistrationFlow(), errorMessage);
+        return processFlow(execution, clientSession, REGISTRATION_PATH, realm.getRegistrationFlow(), errorMessage, new AuthenticationProcessor());
     }
 
 
@@ -450,6 +477,64 @@ public class LoginActionsService {
         return processRegistration(execution, clientSession, null);
     }
 
+    @Path(FIRST_BROKER_LOGIN_PATH)
+    @GET
+    public Response firstBrokerLoginGet(@QueryParam("code") String code,
+                                 @QueryParam("execution") String execution) {
+        return firstBrokerLogin(code, execution);
+    }
+
+    @Path(FIRST_BROKER_LOGIN_PATH)
+    @POST
+    public Response firstBrokerLoginPost(@QueryParam("code") String code,
+                                        @QueryParam("execution") String execution) {
+        return firstBrokerLogin(code, execution);
+    }
+
+    protected Response firstBrokerLogin(String code, String execution) {
+        event.event(EventType.IDENTITY_PROVIDER_FIRST_LOGIN);
+
+        Checks checks = new Checks();
+        if (!checks.verifyCode(code, ClientSessionModel.Action.AUTHENTICATE.name())) {
+            return checks.response;
+        }
+        event.detail(Details.CODE_ID, code);
+        ClientSessionCode clientSessionCode = checks.clientCode;
+        ClientSessionModel clientSession = clientSessionCode.getClientSession();
+
+        SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromClientSession(clientSession);
+        if (serializedCtx == null) {
+            throw new WebApplicationException(ErrorPage.error(session, "Not found serialized context in clientSession"));
+        }
+        BrokeredIdentityContext brokerContext = serializedCtx.deserialize(session, clientSession);
+        AuthenticationFlowModel firstBrokerLoginFlow = realm.getAuthenticationFlowById(brokerContext.getIdpConfig().getFirstBrokerLoginFlowId());
+
+        event.detail(Details.IDENTITY_PROVIDER, brokerContext.getIdpConfig().getAlias())
+                .detail(Details.IDENTITY_PROVIDER_USERNAME, brokerContext.getUsername());
+
+
+        AuthenticationProcessor processor = new AuthenticationProcessor() {
+
+            @Override
+            protected Response authenticationComplete() {
+                return redirectToAfterFirstBrokerLoginEndpoint(clientSession);
+            }
+
+        };
+
+        return processFlow(execution, clientSession, FIRST_BROKER_LOGIN_PATH, firstBrokerLoginFlow, null, processor);
+    }
+
+    private Response redirectToAfterFirstBrokerLoginEndpoint(ClientSessionModel clientSession) {
+        ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession);
+        clientSession.setTimestamp(Time.currentTime());
+
+        URI redirect = Urls.identityProviderAfterFirstBrokerLogin(uriInfo.getBaseUri(), realm.getName(), accessCode.getCode());
+        logger.debugf("Redirecting to '%s' ", redirect);
+
+        return Response.status(302).location(redirect).build();
+    }
+
     /**
      * OAuth grant page.  You should not invoked this directly!
      *
@@ -627,6 +712,10 @@ public class LoginActionsService {
     }
 
     private String getActionCookie() {
+        return getActionCookie(headers, realm, uriInfo, clientConnection);
+    }
+
+    public static String getActionCookie(HttpHeaders headers, RealmModel realm, UriInfo uriInfo, ClientConnection clientConnection) {
         Cookie cookie = headers.getCookies().get(ACTION_COOKIE);
         AuthenticationManager.expireCookie(realm, ACTION_COOKIE, AuthenticationManager.getRealmCookiePath(realm, uriInfo), realm.getSslRequired().isRequired(clientConnection), clientConnection);
         return cookie != null ? cookie.getValue() : null;
diff --git a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
index f18f000..cc1e49a 100755
--- a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
@@ -59,6 +59,10 @@ public class RealmsResource {
         return uriInfo.getBaseUriBuilder().path(RealmsResource.class).path(RealmsResource.class, "getProtocol");
     }
 
+    public static UriBuilder clientRegistrationUrl(UriInfo uriInfo) {
+        return uriInfo.getBaseUriBuilder().path(RealmsResource.class).path(RealmsResource.class, "getClientsService");
+    }
+
     public static UriBuilder brokerUrl(UriInfo uriInfo) {
         return uriInfo.getBaseUriBuilder().path(RealmsResource.class).path(RealmsResource.class, "getBrokerService");
     }
@@ -113,11 +117,11 @@ public class RealmsResource {
         return service;
     }
 
-    @Path("{realm}/client-registration")
+    @Path("{realm}/clients")
     public ClientRegistrationService getClientsService(final @PathParam("realm") String name) {
         RealmModel realm = init(name);
         EventBuilder event = new EventBuilder(realm, session, clientConnection);
-        ClientRegistrationService service = new ClientRegistrationService(realm, event);
+        ClientRegistrationService service = new ClientRegistrationService(event);
         ResteasyProviderFactory.getInstance().injectProperties(service);
         return service;
     }
diff --git a/services/src/main/java/org/keycloak/services/Urls.java b/services/src/main/java/org/keycloak/services/Urls.java
index cac365e..51c6182 100755
--- a/services/src/main/java/org/keycloak/services/Urls.java
+++ b/services/src/main/java/org/keycloak/services/Urls.java
@@ -32,6 +32,8 @@ import org.keycloak.services.resources.RealmsResource;
 import org.keycloak.services.resources.ThemeResource;
 
 import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+
 import java.net.URI;
 
 /**
@@ -94,6 +96,13 @@ public class Urls {
         return identityProviderAuthnRequest(baseURI, providerId, realmName, null);
     }
 
+    public static URI identityProviderAfterFirstBrokerLogin(URI baseUri, String realmName, String accessCode) {
+        return realmBase(baseUri).path(RealmsResource.class, "getBrokerService")
+                .path(IdentityBrokerService.class, "afterFirstBrokerLogin")
+                .replaceQueryParam(OAuth2Constants.CODE, accessCode)
+                .build(realmName);
+    }
+
     public static URI accountTotpPage(URI baseUri, String realmId) {
         return accountBase(baseUri).path(AccountService.class, "totpPage").build(realmId);
     }
@@ -204,6 +213,11 @@ public class Urls {
         return loginActionsBase(baseUri).path(LoginActionsService.class, "processConsent").build(realmId);
     }
 
+    public static URI firstBrokerLoginProcessor(URI baseUri, String realmName) {
+        return loginActionsBase(baseUri).path(LoginActionsService.class, "firstBrokerLoginGet")
+                .build(realmName);
+    }
+
     public static String localeCookiePath(URI baseUri, String realmName){
         return realmBase(baseUri).path(realmName).build().getRawPath();
     }
diff --git a/services/src/main/java/org/keycloak/services/validation/Validation.java b/services/src/main/java/org/keycloak/services/validation/Validation.java
index d3abc4d..0a3c9bb 100755
--- a/services/src/main/java/org/keycloak/services/validation/Validation.java
+++ b/services/src/main/java/org/keycloak/services/validation/Validation.java
@@ -1,8 +1,8 @@
 package org.keycloak.services.validation;
 
+import org.keycloak.authentication.requiredactions.util.UpdateProfileContext;
 import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
-import org.keycloak.models.UserModel;
 import org.keycloak.models.utils.FormMessage;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.services.messages.Messages;
@@ -68,13 +68,13 @@ public class Validation {
     }
 
     public static List<FormMessage> validateUpdateProfileForm(MultivaluedMap<String, String> formData) {
-        return validateUpdateProfileForm(null, formData);
+        return validateUpdateProfileForm(false, formData);
     }
 
-    public static List<FormMessage> validateUpdateProfileForm(RealmModel realm, MultivaluedMap<String, String> formData) {
+    public static List<FormMessage> validateUpdateProfileForm(boolean editUsernameAllowed, MultivaluedMap<String, String> formData) {
         List<FormMessage> errors = new ArrayList<>();
         
-        if (realm != null && realm.isEditUsernameAllowed() && isBlank(formData.getFirst(FIELD_USERNAME))) {
+        if (editUsernameAllowed && isBlank(formData.getFirst(FIELD_USERNAME))) {
             addError(errors, FIELD_USERNAME, Messages.MISSING_USERNAME);
         }
 
@@ -102,7 +102,7 @@ public class Validation {
      * @param user to validate
      * @return true if user object contains all mandatory values, false if some mandatory value is missing
      */
-    public static boolean validateUserMandatoryFields(RealmModel realm, UserModel user){
+    public static boolean validateUserMandatoryFields(RealmModel realm, UpdateProfileContext user){
         return!(isBlank(user.getFirstName()) || isBlank(user.getLastName()) || isBlank(user.getEmail()));        
     }
 
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory b/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
index d6cada5..70551a1 100755
--- a/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
+++ b/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
@@ -9,3 +9,8 @@ org.keycloak.authentication.authenticators.resetcred.ResetCredentialChooseUser
 org.keycloak.authentication.authenticators.resetcred.ResetCredentialEmail
 org.keycloak.authentication.authenticators.resetcred.ResetOTP
 org.keycloak.authentication.authenticators.resetcred.ResetPassword
+org.keycloak.authentication.authenticators.broker.IdpReviewProfileAuthenticatorFactory
+org.keycloak.authentication.authenticators.broker.IdpCreateUserIfUniqueAuthenticatorFactory
+org.keycloak.authentication.authenticators.broker.IdpConfirmLinkAuthenticatorFactory
+org.keycloak.authentication.authenticators.broker.IdpEmailVerificationAuthenticatorFactory
+org.keycloak.authentication.authenticators.broker.IdpUsernamePasswordFormFactory
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper b/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper
index e7a6450..59f0f29 100755
--- a/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper
+++ b/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper
@@ -6,5 +6,6 @@ org.keycloak.protocol.oidc.mappers.HardcodedClaim
 org.keycloak.protocol.oidc.mappers.HardcodedRole
 org.keycloak.protocol.oidc.mappers.RoleNameMapper
 org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper
+org.keycloak.protocol.oidc.mappers.GroupMembershipMapper
 
 
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.services.clientregistration.ClientRegistrationProviderFactory b/services/src/main/resources/META-INF/services/org.keycloak.services.clientregistration.ClientRegistrationProviderFactory
index 3e8773a..d9b8c41 100644
--- a/services/src/main/resources/META-INF/services/org.keycloak.services.clientregistration.ClientRegistrationProviderFactory
+++ b/services/src/main/resources/META-INF/services/org.keycloak.services.clientregistration.ClientRegistrationProviderFactory
@@ -1 +1,3 @@
-org.keycloak.services.clientregistration.DefaultClientRegistrationProviderFactory
\ No newline at end of file
+org.keycloak.services.clientregistration.DefaultClientRegistrationProviderFactory
+org.keycloak.services.clientregistration.oidc.OIDCClientRegistrationProviderFactory
+org.keycloak.services.clientregistration.AdapterInstallationClientRegistrationProviderFactory
\ No newline at end of file
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ClientInitialAccessTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ClientInitialAccessTest.java
new file mode 100644
index 0000000..79ef1ac
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ClientInitialAccessTest.java
@@ -0,0 +1,58 @@
+package org.keycloak.testsuite.admin;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.ClientInitialAccessResource;
+import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
+import org.keycloak.representations.idm.ClientInitialAccessPresentation;
+
+import javax.ws.rs.core.Response;
+import java.net.URI;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientInitialAccessTest extends AbstractClientTest {
+
+    @Test
+    public void create() {
+        ClientInitialAccessResource resource = keycloak.realm(REALM_NAME).clientInitialAccess();
+
+        ClientInitialAccessPresentation access = resource.create(new ClientInitialAccessCreatePresentation(1000, 2));
+        Assert.assertEquals(new Integer(2), access.getCount());
+        Assert.assertEquals(new Integer(2), access.getRemainingCount());
+        Assert.assertEquals(new Integer(1000), access.getExpiration());
+        Assert.assertNotNull(access.getTimestamp());
+        Assert.assertNotNull(access.getToken());
+
+        ClientInitialAccessPresentation access2 = resource.create(new ClientInitialAccessCreatePresentation());
+
+        List<ClientInitialAccessPresentation> list = resource.list();
+        Assert.assertEquals(2, list.size());
+
+        for (ClientInitialAccessPresentation r : list) {
+            if (r.getId().equals(access.getId())) {
+                Assert.assertEquals(new Integer(2), r.getCount());
+                Assert.assertEquals(new Integer(2), r.getRemainingCount());
+                Assert.assertEquals(new Integer(1000), r.getExpiration());
+                Assert.assertNotNull(r.getTimestamp());
+                Assert.assertNull(r.getToken());
+            } else if(r.getId().equals(access2.getId())) {
+                Assert.assertEquals(new Integer(1), r.getCount());
+                Assert.assertEquals(new Integer(1), r.getRemainingCount());
+                Assert.assertEquals(new Integer(0), r.getExpiration());
+                Assert.assertNotNull(r.getTimestamp());
+                Assert.assertNull(r.getToken());
+            } else {
+                Assert.fail("Unexpected id");
+            }
+        }
+
+        resource.delete(access.getId());
+        resource.delete(access2.getId());
+
+        Assert.assertTrue(resource.list().isEmpty());
+    }
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java
index a7bae96..f88fb4a 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java
@@ -52,7 +52,6 @@ public class IdentityProviderTest extends AbstractClientTest {
         assertEquals("clientSecret", representation.getConfig().get("clientSecret"));
         assertTrue(representation.isEnabled());
         assertFalse(representation.isStoreToken());
-        assertEquals(IdentityProviderRepresentation.UPFLM_ON, representation.getUpdateProfileFirstLoginMode());
         assertFalse(representation.isTrustEmail());
     }
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java
index bb33515..42870a6 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java
@@ -180,7 +180,6 @@ public class RealmTest extends AbstractClientTest {
         String description = IOUtils.toString(getClass().getResourceAsStream("/client-descriptions/client-oidc.json"));
 
         ClientRepresentation converted = realm.convertClientDescription(description);
-        assertEquals(36, converted.getClientId().length());
         assertEquals(1, converted.getRedirectUris().size());
         assertEquals("http://localhost", converted.getRedirectUris().get(0));
     }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java
new file mode 100644
index 0000000..7d780cd
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java
@@ -0,0 +1,397 @@
+package org.keycloak.testsuite.broker;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.mail.internet.MimeMessage;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.authentication.authenticators.broker.IdpCreateUserIfUniqueAuthenticator;
+import org.keycloak.authentication.authenticators.broker.IdpCreateUserIfUniqueAuthenticatorFactory;
+import org.keycloak.authentication.authenticators.broker.IdpEmailVerificationAuthenticatorFactory;
+import org.keycloak.authentication.authenticators.broker.IdpReviewProfileAuthenticatorFactory;
+import org.keycloak.common.util.ObjectUtil;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.AuthenticationFlowModel;
+import org.keycloak.models.AuthenticatorConfigModel;
+import org.keycloak.models.FederatedIdentityModel;
+import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.DefaultAuthenticationFlows;
+import org.keycloak.representations.idm.IdentityProviderRepresentation;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.pages.IdpConfirmLinkPage;
+import org.keycloak.testsuite.pages.IdpLinkEmailPage;
+import org.keycloak.testsuite.pages.LoginPasswordResetPage;
+import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
+import org.keycloak.testsuite.pages.LoginUpdateProfileEditUsernameAllowedPage;
+import org.keycloak.testsuite.rule.KeycloakRule;
+import org.keycloak.testsuite.rule.WebResource;
+import org.openqa.selenium.By;
+import org.openqa.selenium.NoSuchElementException;
+import org.openqa.selenium.WebElement;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public abstract class AbstractFirstBrokerLoginTest extends AbstractIdentityProviderTest {
+
+    protected static final String APP_REALM_ID = "realm-with-broker";
+
+    @WebResource
+    protected LoginUpdateProfileEditUsernameAllowedPage updateProfileWithUsernamePage;
+
+    @WebResource
+    protected IdpConfirmLinkPage idpConfirmLinkPage;
+
+    @WebResource
+    protected IdpLinkEmailPage idpLinkEmailPage;
+
+    @WebResource
+    protected LoginPasswordUpdatePage passwordUpdatePage;
+
+
+
+    /**
+     * Tests that if updateProfile is off and CreateUserIfUnique authenticator mandatory, error page will be shown if user with same email already exists
+     */
+    @Test
+    public void testErrorPageWhenDuplicationNotAllowed_updateProfileOff() {
+        brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
+                setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW, IdpCreateUserIfUniqueAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.REQUIRED);
+                setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_OFF);
+            }
+
+        }, APP_REALM_ID);
+
+        loginIDP("pedroigor");
+
+        WebElement element = this.driver.findElement(By.className("instruction"));
+
+        assertNotNull(element);
+
+        assertEquals("User with email psilva@redhat.com already exists. Please login to account management to link the account.", element.getText());
+
+        brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
+                setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW, IdpCreateUserIfUniqueAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.ALTERNATIVE);
+            }
+
+        }, APP_REALM_ID);
+    }
+
+
+    /**
+     * Tests that if updateProfile is on and CreateUserIfUnique authenticator mandatory, error page will be shown if user with same email already exists
+     */
+    @Test
+    public void testErrorPageWhenDuplicationNotAllowed_updateProfileOn() {
+        brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
+                setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW, IdpCreateUserIfUniqueAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.REQUIRED);
+                setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_ON);
+            }
+
+        }, APP_REALM_ID);
+
+        loginIDP("test-user");
+
+        this.updateProfileWithUsernamePage.assertCurrent();
+        this.updateProfileWithUsernamePage.update("Test", "User", "test-user@redhat.com", "pedroigor");
+
+        WebElement element = this.driver.findElement(By.className("instruction"));
+
+        assertNotNull(element);
+
+        assertEquals("User with username pedroigor already exists. Please login to account management to link the account.", element.getText());
+
+        brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
+                setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW, IdpCreateUserIfUniqueAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.ALTERNATIVE);
+            }
+
+        }, APP_REALM_ID);
+    }
+
+
+    /**
+     * Test user registers with IdentityProvider and needs to update password when it's required by IdpCreateUserIfUniqueAuthenticator
+     */
+    @Test
+    public void testRegistrationWithPasswordUpdateRequired() {
+        // Require updatePassword after user registered with broker
+        brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
+                AuthenticatorConfigModel authenticatorConfig = realmWithBroker.getAuthenticatorConfigByAlias(DefaultAuthenticationFlows.IDP_CREATE_UNIQUE_USER_CONFIG_ALIAS);
+                authenticatorConfig.getConfig().put(IdpCreateUserIfUniqueAuthenticatorFactory.REQUIRE_PASSWORD_UPDATE_AFTER_REGISTRATION, "true");
+                realmWithBroker.updateAuthenticatorConfig(authenticatorConfig);
+
+                setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_MISSING);
+            }
+
+        }, APP_REALM_ID);
+
+        loginIDP("pedroigor");
+        this.updateProfileWithUsernamePage.assertCurrent();
+        this.updateProfileWithUsernamePage.update("Test", "User", "some-user@redhat.com", "some-user");
+
+        // Need to update password now
+        this.passwordUpdatePage.assertCurrent();
+        this.passwordUpdatePage.changePassword("password1", "password1");
+
+
+        // assert authenticated
+        assertFederatedUser("some-user", "some-user@redhat.com", "pedroigor");
+
+
+        brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
+                AuthenticatorConfigModel authenticatorConfig = realmWithBroker.getAuthenticatorConfigByAlias(DefaultAuthenticationFlows.IDP_CREATE_UNIQUE_USER_CONFIG_ALIAS);
+                authenticatorConfig.getConfig().put(IdpCreateUserIfUniqueAuthenticatorFactory.REQUIRE_PASSWORD_UPDATE_AFTER_REGISTRATION, "false");
+                realmWithBroker.updateAuthenticatorConfig(authenticatorConfig);
+            }
+
+        }, APP_REALM_ID);
+    }
+
+
+    /**
+     * Tests that duplication is detected, the confirmation page is displayed, user clicks on "Review profile" and goes back to updateProfile page and resolves duplication
+     * by create new user
+     */
+    @Test
+    public void testFixDuplicationsByReviewProfile() {
+        setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
+
+        loginIDP("pedroigor");
+
+        // There is user with same email. Update profile to use different email
+        this.idpConfirmLinkPage.assertCurrent();
+        Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
+        this.idpConfirmLinkPage.clickReviewProfile();
+
+        this.updateProfileWithUsernamePage.assertCurrent();
+        this.updateProfileWithUsernamePage.update("Test", "User", "testing-user@redhat.com", "pedroigor");
+
+        // There is user with same username. Update profile to use different username
+        this.idpConfirmLinkPage.assertCurrent();
+        Assert.assertEquals("User with username pedroigor already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
+        this.idpConfirmLinkPage.clickReviewProfile();
+
+        this.updateProfileWithUsernamePage.assertCurrent();
+        this.updateProfileWithUsernamePage.update("Test", "User", "testing-user@redhat.com", "testing-user");
+
+        assertFederatedUser("testing-user", "testing-user@redhat.com", "pedroigor");
+    }
+
+    /**
+     * Tests that duplication is detected and user wants to link federatedIdentity with existing account. He will confirm link by email
+     */
+    @Test
+    public void testLinkAccountByEmailVerification() throws Exception {
+        setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
+
+        loginIDP("pedroigor");
+
+        this.idpConfirmLinkPage.assertCurrent();
+        Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
+        this.idpConfirmLinkPage.clickLinkAccount();
+
+        // Confirm linking account by email
+        this.idpLinkEmailPage.assertCurrent();
+        Assert.assertEquals("An email with instructions to link " + ObjectUtil.capitalize(getProviderId()) + " account pedroigor with your " + APP_REALM_ID + " account has been sent to you.", this.idpLinkEmailPage.getMessage());
+
+        Assert.assertEquals(1, greenMail.getReceivedMessages().length);
+        MimeMessage message = greenMail.getReceivedMessages()[0];
+        String linkFromMail = getVerificationEmailLink(message);
+
+        driver.navigate().to(linkFromMail.trim());
+
+        // authenticated and redirected to app. User is linked with identity provider
+        assertFederatedUser("pedroigor", "psilva@redhat.com", "pedroigor");
+    }
+
+
+    /**
+     * Tests that duplication is detected and user wants to link federatedIdentity with existing account. He will confirm link by reauthentication (confirm password on login screen)
+     */
+    @Test
+    public void testLinkAccountByReauthenticationWithPassword() throws Exception {
+        // Remove smtp config. The reauthentication by username+password screen will be automatically used then
+        final Map<String, String> smtpConfig = new HashMap<>();
+        brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
+                setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_OFF);
+                smtpConfig.putAll(realmWithBroker.getSmtpConfig());
+                realmWithBroker.setSmtpConfig(Collections.<String, String>emptyMap());
+            }
+
+        }, APP_REALM_ID);
+
+
+        loginIDP("pedroigor");
+
+
+        this.idpConfirmLinkPage.assertCurrent();
+        Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
+        this.idpConfirmLinkPage.clickLinkAccount();
+
+        // Login screen shown. Username is prefilled and disabled. Registration link and social buttons are not shown
+        Assert.assertEquals("Log in to " + APP_REALM_ID, this.driver.getTitle());
+        Assert.assertEquals("pedroigor", this.loginPage.getUsername());
+        Assert.assertFalse(this.loginPage.isUsernameInputEnabled());
+
+        Assert.assertEquals("Authenticate as pedroigor to link your account with " + getProviderId(), this.loginPage.getSuccessMessage());
+
+        try {
+            this.loginPage.findSocialButton(getProviderId());
+            Assert.fail("Not expected to see social button with " + getProviderId());
+        } catch (NoSuchElementException expected) {
+        }
+
+        try {
+            this.loginPage.clickRegister();
+            Assert.fail("Not expected to see register link");
+        } catch (NoSuchElementException expected) {
+        }
+
+        // Use bad password first
+        this.loginPage.login("password1");
+        Assert.assertEquals("Invalid username or password.", this.loginPage.getError());
+
+        // Use correct password now
+        this.loginPage.login("password");
+
+        // authenticated and redirected to app. User is linked with identity provider
+        assertFederatedUser("pedroigor", "psilva@redhat.com", "pedroigor");
+
+
+        // Restore smtp config
+        brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
+                realmWithBroker.setSmtpConfig(smtpConfig);
+            }
+
+        }, APP_REALM_ID);
+    }
+
+
+    /**
+     * Tests that duplication is detected and user wants to link federatedIdentity with existing account. He will confirm link by reauthentication (confirm password on login screen)
+     * and additionally he goes through "forget password"
+     */
+    @Test
+    public void testLinkAccountByReauthentication_forgetPassword() throws Exception {
+        brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
+                setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_HANDLE_EXISTING_SUBFLOW,
+                        IdpEmailVerificationAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.DISABLED);
+
+                setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_OFF);
+            }
+
+        }, APP_REALM_ID);
+
+        loginIDP("pedroigor");
+
+        this.idpConfirmLinkPage.assertCurrent();
+        Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
+        this.idpConfirmLinkPage.clickLinkAccount();
+
+        // Click "Forget password" on login page. Email sent directly because username is known
+        Assert.assertEquals("Log in to " + APP_REALM_ID, this.driver.getTitle());
+        this.loginPage.resetPassword();
+
+        Assert.assertEquals("Log in to " + APP_REALM_ID, this.driver.getTitle());
+        Assert.assertEquals("You should receive an email shortly with further instructions.", this.loginPage.getSuccessMessage());
+
+        // Click on link from email
+        Assert.assertEquals(1, greenMail.getReceivedMessages().length);
+        MimeMessage message = greenMail.getReceivedMessages()[0];
+        String linkFromMail = getVerificationEmailLink(message);
+
+        driver.navigate().to(linkFromMail.trim());
+
+        // Need to update password now
+        this.passwordUpdatePage.assertCurrent();
+        this.passwordUpdatePage.changePassword("password", "password");
+
+        // authenticated and redirected to app. User is linked with identity provider
+        assertFederatedUser("pedroigor", "psilva@redhat.com", "pedroigor");
+
+        brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
+                setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_HANDLE_EXISTING_SUBFLOW,
+                        IdpEmailVerificationAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.ALTERNATIVE);
+
+            }
+
+        }, APP_REALM_ID);
+    }
+
+
+    protected void assertFederatedUser(String expectedUsername, String expectedEmail, String expectedFederatedUsername) {
+        assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app"));
+        UserModel federatedUser = getFederatedUser();
+
+        assertNotNull(federatedUser);
+        assertEquals(expectedUsername, federatedUser.getUsername());
+        assertEquals(expectedEmail, federatedUser.getEmail());
+
+        RealmModel realmWithBroker = getRealm();
+        Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realmWithBroker);
+        assertEquals(1, federatedIdentities.size());
+
+        FederatedIdentityModel federatedIdentityModel = federatedIdentities.iterator().next();
+
+        assertEquals(getProviderId(), federatedIdentityModel.getIdentityProvider());
+        assertEquals(expectedFederatedUsername, federatedIdentityModel.getUserName());
+    }
+
+
+    protected void setExecutionRequirement(RealmModel realmWithBroker, String flowAlias, String authenticatorProvider, AuthenticationExecutionModel.Requirement requirement) {
+        AuthenticationFlowModel flowModel = realmWithBroker.getFlowByAlias(flowAlias);
+        List<AuthenticationExecutionModel> authExecutions = realmWithBroker.getAuthenticationExecutions(flowModel.getId());
+        for (AuthenticationExecutionModel execution : authExecutions) {
+            if (execution.getAuthenticator().equals(authenticatorProvider)) {
+                execution.setRequirement(requirement);
+                realmWithBroker.updateAuthenticatorExecution(execution);
+                return;
+            }
+        }
+
+        throw new IllegalStateException("Execution not found for flow " + flowAlias + " and authenticator " + authenticatorProvider);
+    }
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
index d7084c1..5248c38 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
@@ -23,20 +23,26 @@ import org.junit.Before;
 import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
+import org.keycloak.authentication.authenticators.broker.IdpReviewProfileAuthenticatorFactory;
+import org.keycloak.models.AuthenticatorConfigModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.Constants;
 import org.keycloak.models.FederatedIdentityModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionTask;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserModel.RequiredAction;
+import org.keycloak.models.utils.DefaultAuthenticationFlows;
+import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.representations.IDToken;
 import org.keycloak.representations.idm.IdentityProviderRepresentation;
 import org.keycloak.services.Urls;
 import org.keycloak.testsuite.MailUtil;
 import org.keycloak.testsuite.OAuthClient;
+import org.keycloak.testsuite.broker.util.UserSessionStatusServlet;
 import org.keycloak.testsuite.broker.util.UserSessionStatusServlet.UserSessionStatus;
 import org.keycloak.testsuite.pages.AccountFederatedIdentityPage;
 import org.keycloak.testsuite.pages.AccountPasswordPage;
@@ -82,7 +88,7 @@ import static org.junit.Assert.fail;
  */
 public abstract class AbstractIdentityProviderTest {
 
-    private static final URI BASE_URI = UriBuilder.fromUri("http://localhost:8081/auth").build();
+    protected static final URI BASE_URI = UriBuilder.fromUri("http://localhost:8081/auth").build();
 
     @ClassRule
     public static BrokerKeyCloakRule brokerServerRule = new BrokerKeyCloakRule();
@@ -94,10 +100,10 @@ public abstract class AbstractIdentityProviderTest {
     protected WebDriver driver;
 
     @WebResource
-    private LoginPage loginPage;
+    protected LoginPage loginPage;
 
     @WebResource
-    private LoginUpdateProfilePage updateProfilePage;
+    protected LoginUpdateProfilePage updateProfilePage;
 
 
     @WebResource
@@ -118,7 +124,7 @@ public abstract class AbstractIdentityProviderTest {
     @WebResource
     protected AccountFederatedIdentityPage accountFederatedIdentityPage;
 
-    private KeycloakSession session;
+    protected KeycloakSession session;
 
     @Before
     public void onBefore() {
@@ -135,87 +141,19 @@ public abstract class AbstractIdentityProviderTest {
         brokerServerRule.stopSession(this.session, true);
     }
 
-    @Test
-    public void testSuccessfulAuthentication() {
-        IdentityProviderModel identityProviderModel = getIdentityProviderModel();
-        identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_ON);
-
-        UserModel user = assertSuccessfulAuthentication(identityProviderModel, "test-user", "new@email.com", true);
-        Assert.assertEquals("617-666-7777", user.getFirstAttribute("mobile"));
-    }
-
-    @Test
-    public void testSuccessfulAuthenticationUpdateProfileOnMissing_nothingMissing() {
-        IdentityProviderModel identityProviderModel = getIdentityProviderModel();
-        identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_MISSING);
-
-        assertSuccessfulAuthentication(identityProviderModel, "test-user", "test-user@localhost", false);
-    }
-
-    @Test
-    public void testSuccessfulAuthenticationUpdateProfileOnMissing_missingEmail() {
-        IdentityProviderModel identityProviderModel = getIdentityProviderModel();
-        identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_MISSING);
-
-        assertSuccessfulAuthentication(identityProviderModel, "test-user-noemail", "new@email.com", true);
-    }
-
-    @Test
-    public void testSuccessfulAuthenticationWithoutUpdateProfile() {
-        IdentityProviderModel identityProviderModel = getIdentityProviderModel();
-        identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_OFF);
-
-        assertSuccessfulAuthentication(identityProviderModel, "test-user", "test-user@localhost", false);
-    }
-
-    /**
-     * Test that verify email action is performed if email is provided and email trust is not enabled for the provider
-     * 
-     * @throws MessagingException
-     * @throws IOException
-     */
-    @Test
-    public void testSuccessfulAuthenticationWithoutUpdateProfile_emailProvided_emailVerifyEnabled() throws IOException, MessagingException {
-        getRealm().setVerifyEmail(true);
-        brokerServerRule.stopSession(this.session, true);
-        this.session = brokerServerRule.startSession();
-
-        IdentityProviderModel identityProviderModel = getIdentityProviderModel();
-        try {
-            identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_OFF);
-            identityProviderModel.setTrustEmail(false);
-
-            UserModel federatedUser = assertSuccessfulAuthenticationWithEmailVerification(identityProviderModel, "test-user", "test-user@localhost", false);
-
-            // email is verified now
-            assertFalse(federatedUser.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL.name()));
-
-        } finally {
-            getRealm().setVerifyEmail(false);
-        }
-    }
-
-    private UserModel assertSuccessfulAuthenticationWithEmailVerification(IdentityProviderModel identityProviderModel, String username, String expectedEmail,
-            boolean isProfileUpdateExpected)
-            throws IOException, MessagingException {
+    protected UserModel assertSuccessfulAuthentication(IdentityProviderModel identityProviderModel, String username, String expectedEmail, boolean isProfileUpdateExpected) {
         authenticateWithIdentityProvider(identityProviderModel, username, isProfileUpdateExpected);
 
-        // verify email is sent
-        Assert.assertTrue(verifyEmailPage.isCurrent());
-
-        // read email to take verification link from
-        Assert.assertEquals(1, greenMail.getReceivedMessages().length);
-        MimeMessage message = greenMail.getReceivedMessages()[0];
-        String verificationUrl = getVerificationEmailLink(message);
-
-        driver.navigate().to(verificationUrl.trim());
-
         // authenticated and redirected to app
-        assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app"));
+        assertTrue("Bad current URL " + this.driver.getCurrentUrl() + " and page source: " + this.driver.getPageSource(),
+                this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app"));
 
         UserModel federatedUser = getFederatedUser();
 
         assertNotNull(federatedUser);
+        assertNotNull(federatedUser.getCreatedTimestamp());
+        // test that timestamp is current with 10s tollerance
+        Assert.assertTrue((System.currentTimeMillis() - federatedUser.getCreatedTimestamp()) < 10000);
 
         doAssertFederatedUser(federatedUser, identityProviderModel, expectedEmail, isProfileUpdateExpected);
 
@@ -240,166 +178,7 @@ public abstract class AbstractIdentityProviderTest {
         return federatedUser;
     }
 
-    /**
-     * Test for KEYCLOAK-1053 - verify email action is not performed if email is not provided, login is normal, but action stays in set to be performed later
-     */
-    @Test
-    public void testSuccessfulAuthenticationWithoutUpdateProfile_emailNotProvided_emailVerifyEnabled() {
-        getRealm().setVerifyEmail(true);
-        brokerServerRule.stopSession(this.session, true);
-        this.session = brokerServerRule.startSession();
-
-        try {
-            IdentityProviderModel identityProviderModel = getIdentityProviderModel();
-            identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_OFF);
-
-            UserModel federatedUser = assertSuccessfulAuthentication(identityProviderModel, "test-user-noemail", null, false);
-
-            assertTrue(federatedUser.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL.name()));
-
-        } finally {
-            getRealm().setVerifyEmail(false);
-        }
-    }
-
-    /**
-     * Test for KEYCLOAK-1372 - verify email action is not performed if email is provided but email trust is enabled for the provider
-     */
-    @Test
-    public void testSuccessfulAuthenticationWithoutUpdateProfile_emailProvided_emailVerifyEnabled_emailTrustEnabled() {
-        getRealm().setVerifyEmail(true);
-        brokerServerRule.stopSession(this.session, true);
-        this.session = brokerServerRule.startSession();
-
-        IdentityProviderModel identityProviderModel = getIdentityProviderModel();
-        try {
-            identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_OFF);
-            identityProviderModel.setTrustEmail(true);
-
-            UserModel federatedUser = assertSuccessfulAuthentication(identityProviderModel, "test-user", "test-user@localhost", false);
-
-            assertFalse(federatedUser.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL.name()));
-
-        } finally {
-            identityProviderModel.setTrustEmail(false);
-            getRealm().setVerifyEmail(false);
-        }
-    }
-
-    /**
-     * Test for KEYCLOAK-1372 - verify email action is performed if email is provided and email trust is enabled for the provider, but email is changed on First login update profile page
-     * 
-     * @throws MessagingException
-     * @throws IOException
-     */
-    @Test
-    public void testSuccessfulAuthentication_emailTrustEnabled_emailVerifyEnabled_emailUpdatedOnFirstLogin() throws IOException, MessagingException {
-        getRealm().setVerifyEmail(true);
-        brokerServerRule.stopSession(this.session, true);
-        this.session = brokerServerRule.startSession();
-
-        IdentityProviderModel identityProviderModel = getIdentityProviderModel();
-        try {
-            identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_ON);
-            identityProviderModel.setTrustEmail(true);
-
-            UserModel user = assertSuccessfulAuthenticationWithEmailVerification(identityProviderModel, "test-user", "new@email.com", true);
-            Assert.assertEquals("617-666-7777", user.getFirstAttribute("mobile"));
-        } finally {
-            identityProviderModel.setTrustEmail(false);
-            getRealm().setVerifyEmail(false);
-        }
-    }
-
-    @Test
-    public void testSuccessfulAuthenticationWithoutUpdateProfile_newUser_emailAsUsername() {
-
-        getRealm().setRegistrationEmailAsUsername(true);
-        brokerServerRule.stopSession(this.session, true);
-        this.session = brokerServerRule.startSession();
-
-        try {
-            IdentityProviderModel identityProviderModel = getIdentityProviderModel();
-            identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_OFF);
-
-            authenticateWithIdentityProvider(identityProviderModel, "test-user", false);
-
-            // authenticated and redirected to app
-            assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app"));
-
-            brokerServerRule.stopSession(session, true);
-            session = brokerServerRule.startSession();
-
-            // check correct user is created with email as username and bound to correct federated identity
-            RealmModel realm = getRealm();
-
-            UserModel federatedUser = session.users().getUserByUsername("test-user@localhost", realm);
-
-            assertNotNull(federatedUser);
-
-            assertEquals("test-user@localhost", federatedUser.getUsername());
-
-            doAssertFederatedUser(federatedUser, identityProviderModel, "test-user@localhost", false);
-
-            Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm);
-
-            assertEquals(1, federatedIdentities.size());
-
-            FederatedIdentityModel federatedIdentityModel = federatedIdentities.iterator().next();
-
-            assertEquals(getProviderId(), federatedIdentityModel.getIdentityProvider());
-
-            driver.navigate().to("http://localhost:8081/test-app/logout");
-            driver.navigate().to("http://localhost:8081/test-app");
-
-            assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
-
-        } finally {
-            getRealm().setRegistrationEmailAsUsername(false);
-        }
-    }
-
-    @Test
-    public void testSuccessfulAuthenticationWithoutUpdateProfile_newUser_emailAsUsername_emailNotProvided() {
-
-        getRealm().setRegistrationEmailAsUsername(true);
-        brokerServerRule.stopSession(this.session, true);
-        this.session = brokerServerRule.startSession();
-
-        try {
-            IdentityProviderModel identityProviderModel = getIdentityProviderModel();
-            identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_OFF);
-
-            authenticateWithIdentityProvider(identityProviderModel, "test-user-noemail", false);
-
-            brokerServerRule.stopSession(session, true);
-            session = brokerServerRule.startSession();
-
-            // check correct user is created with username from provider as email is not available
-            RealmModel realm = getRealm();
-            UserModel federatedUser = getFederatedUser();
-            assertNotNull(federatedUser);
-
-            doAssertFederatedUserNoEmail(federatedUser);
-
-            Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm);
-
-            assertEquals(1, federatedIdentities.size());
-
-            FederatedIdentityModel federatedIdentityModel = federatedIdentities.iterator().next();
-
-            assertEquals(getProviderId(), federatedIdentityModel.getIdentityProvider());
-            revokeGrant();
 
-            driver.navigate().to("http://localhost:8081/test-app/logout");
-            driver.navigate().to("http://localhost:8081/test-app");
-
-            assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
-
-        } finally {
-            getRealm().setRegistrationEmailAsUsername(false);
-        }
-    }
 
     protected void doAssertFederatedUserNoEmail(UserModel federatedUser) {
         assertEquals("kc-oidc-idp.test-user-noemail", federatedUser.getUsername());
@@ -408,312 +187,7 @@ public abstract class AbstractIdentityProviderTest {
         assertEquals("User", federatedUser.getLastName());
     }
 
-    @Test
-    public void testDisabled() {
-        IdentityProviderModel identityProviderModel = getIdentityProviderModel();
-
-        identityProviderModel.setEnabled(false);
-
-        this.driver.navigate().to("http://localhost:8081/test-app/");
-
-        assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
-
-        try {
-            this.driver.findElement(By.className(getProviderId()));
-            fail("Provider [" + getProviderId() + "] not disabled.");
-        } catch (NoSuchElementException nsee) {
-
-        }
-    }
-
-    @Test
-    public void testProviderOnLoginPage() {
-        // Provider button is available on login page
-        this.driver.navigate().to("http://localhost:8081/test-app/");
-        assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
-        loginPage.findSocialButton(getProviderId());
-     }
-
-    @Test
-    public void testUserAlreadyExistsWhenUpdatingProfile() {
-        this.driver.navigate().to("http://localhost:8081/test-app/");
-
-        assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
-
-        // choose the identity provider
-        this.loginPage.clickSocial(getProviderId());
-
-        assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/"));
-
-        // log in to identity provider
-        this.loginPage.login("test-user", "password");
-
-        doAfterProviderAuthentication();
-
-        this.updateProfilePage.assertCurrent();
-        this.updateProfilePage.update("Test", "User", "psilva@redhat.com");
-
-        WebElement element = this.driver.findElement(By.className("kc-feedback-text"));
-
-        assertNotNull(element);
-
-        assertEquals("Email already exists.", element.getText());
-
-        this.updateProfilePage.assertCurrent();
-        this.updateProfilePage.update("Test", "User", "test-user@redhat.com");
-
-        assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app"));
-
-        UserModel federatedUser = getFederatedUser();
-
-        assertNotNull(federatedUser);
-    }
-
-    @Test
-    public void testUserAlreadyExistsWhenNotUpdatingProfile() {
-        IdentityProviderModel identityProviderModel = getIdentityProviderModel();
-
-        identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_OFF);
-
-        this.driver.navigate().to("http://localhost:8081/test-app/");
-
-        assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
-
-        // choose the identity provider
-        this.loginPage.clickSocial(getProviderId());
-
-        assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/"));
-
-        // log in to identity provider
-        this.loginPage.login("pedroigor", "password");
-
-        doAfterProviderAuthentication();
-
-        WebElement element = this.driver.findElement(By.className("kc-feedback-text"));
-
-        assertNotNull(element);
-
-        assertEquals("User with email already exists. Please login to account management to link the account.", element.getText());
-    }
-
-    @Test
-    public void testAccountManagementLinkIdentity() {
-        // Login as pedroigor to account management
-        accountFederatedIdentityPage.realm("realm-with-broker");
-        accountFederatedIdentityPage.open();
-        assertTrue(driver.getTitle().equals("Log in to realm-with-broker"));
-        loginPage.login("pedroigor", "password");
-        assertTrue(accountFederatedIdentityPage.isCurrent());
-
-        // Link my "pedroigor" identity with "test-user" from brokered Keycloak
-        IdentityProviderModel identityProviderModel = getIdentityProviderModel();
-        accountFederatedIdentityPage.clickAddProvider(identityProviderModel.getAlias());
-
-        assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/"));
-        this.loginPage.login("test-user", "password");
-        doAfterProviderAuthentication();
-
-        // Assert identity linked in account management
-        assertTrue(accountFederatedIdentityPage.isCurrent());
-        assertTrue(driver.getPageSource().contains("id=\"remove-" + identityProviderModel.getAlias() + "\""));
-
-        // Revoke grant in account mgmt
-        revokeGrant();
-
-        // Logout from account management
-        accountFederatedIdentityPage.logout();
-        assertTrue(driver.getTitle().equals("Log in to realm-with-broker"));
-
-        // Assert I am logged immediately to account management due to previously linked "test-user" identity
-        loginPage.clickSocial(identityProviderModel.getAlias());
-        doAfterProviderAuthentication();
-        assertTrue(accountFederatedIdentityPage.isCurrent());
-        assertTrue(driver.getPageSource().contains("id=\"remove-" + identityProviderModel.getAlias() + "\""));
-
-        // Unlink my "test-user"
-        accountFederatedIdentityPage.clickRemoveProvider(identityProviderModel.getAlias());
-        assertTrue(driver.getPageSource().contains("id=\"add-" + identityProviderModel.getAlias() + "\""));
-
-        // Revoke grant in account mgmt
-        revokeGrant();
-
-        // Logout from account management
-        System.out.println("*** logout from account management");
-        accountFederatedIdentityPage.logout();
-        assertTrue(driver.getTitle().equals("Log in to realm-with-broker"));
-        assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
-
-        // Try to login. Previous link is not valid anymore, so now it should try to register new user
-        this.loginPage.clickSocial(identityProviderModel.getAlias());
-        this.loginPage.login("test-user", "password");
-        doAfterProviderAuthentication();
-        this.updateProfilePage.assertCurrent();
-    }
-
-    @Test(expected = NoSuchElementException.class)
-    public void testIdentityProviderNotAllowed() {
-        this.driver.navigate().to("http://localhost:8081/test-app/");
-
-        assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
-
-        driver.findElement(By.className("model-oidc-idp"));
-    }
-
-    protected void configureClientRetrieveToken(String clientId) {
-        RealmModel realm = getRealm();
-        RoleModel readTokenRole = realm.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID).getRole(Constants.READ_TOKEN_ROLE);
-        ClientModel client = realm.getClientByClientId(clientId);
-        if (!client.hasScope(readTokenRole)) client.addScopeMapping(readTokenRole);
-
-        brokerServerRule.stopSession(session, true);
-        session = brokerServerRule.startSession();
-
-    }
-
-    protected void configureUserRetrieveToken(String username) {
-        RealmModel realm = getRealm();
-        UserModel user = session.users().getUserByUsername(username, realm);
-        RoleModel readTokenRole = realm.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID).getRole(Constants.READ_TOKEN_ROLE);
-        if (user != null && !user.hasRole(readTokenRole)) {
-            user.grantRole(readTokenRole);
-        }
-        brokerServerRule.stopSession(session, true);
-        session = brokerServerRule.startSession();
-
-    }
-
-    protected void unconfigureClientRetrieveToken(String clientId) {
-        RealmModel realm = getRealm();
-        RoleModel readTokenRole = realm.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID).getRole(Constants.READ_TOKEN_ROLE);
-        ClientModel client = realm.getClientByClientId(clientId);
-        if (client.hasScope(readTokenRole)) client.deleteScopeMapping(readTokenRole);
-
-        brokerServerRule.stopSession(session, true);
-        session = brokerServerRule.startSession();
-
-    }
-
-    protected void unconfigureUserRetrieveToken(String username) {
-        RealmModel realm = getRealm();
-        UserModel user = session.users().getUserByUsername(username, realm);
-        RoleModel readTokenRole = realm.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID).getRole(Constants.READ_TOKEN_ROLE);
-        if (user != null && user.hasRole(readTokenRole)) {
-            user.deleteRoleMapping(readTokenRole);
-        }
-        brokerServerRule.stopSession(session, true);
-        session = brokerServerRule.startSession();
-
-    }
-
-    @Test
-    public void testTokenStorageAndRetrievalByApplication() {
-        IdentityProviderModel identityProviderModel = getIdentityProviderModel();
-
-        identityProviderModel.setStoreToken(true);
-
-        authenticateWithIdentityProvider(identityProviderModel, "test-user", true);
-
-        UserModel federatedUser = getFederatedUser();
-        RealmModel realm = getRealm();
-        Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm);
-
-        assertFalse(federatedIdentities.isEmpty());
-        assertEquals(1, federatedIdentities.size());
-
-        FederatedIdentityModel identityModel = federatedIdentities.iterator().next();
-
-        assertNotNull(identityModel.getToken());
-
-        UserSessionStatus userSessionStatus = retrieveSessionStatus();
-        String accessToken = userSessionStatus.getAccessTokenString();
-        URI tokenEndpointUrl = Urls.identityProviderRetrieveToken(BASE_URI, getProviderId(), realm.getName());
-        final String authHeader = "Bearer " + accessToken;
-        ClientRequestFilter authFilter = new ClientRequestFilter() {
-            @Override
-            public void filter(ClientRequestContext requestContext) throws IOException {
-                requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader);
-            }
-        };
-        Client client = ClientBuilder.newBuilder().register(authFilter).build();
-        WebTarget tokenEndpoint = client.target(tokenEndpointUrl);
-        Response response = tokenEndpoint.request().get();
-        assertEquals(Status.OK.getStatusCode(), response.getStatus());
-        assertNotNull(response.readEntity(String.class));
-        revokeGrant();
-
-
-        driver.navigate().to("http://localhost:8081/test-app/logout");
-        String currentUrl = this.driver.getCurrentUrl();
-        System.out.println("after logout currentUrl: " + currentUrl);
-        assertTrue(currentUrl.startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
-
-        unconfigureUserRetrieveToken(getProviderId() + ".test-user");
-        loginIDP("test-user");
-        //authenticateWithIdentityProvider(identityProviderModel, "test-user");
-        assertEquals("http://localhost:8081/test-app", driver.getCurrentUrl());
-
-        userSessionStatus = retrieveSessionStatus();
-        accessToken = userSessionStatus.getAccessTokenString();
-        final String authHeader2 = "Bearer " + accessToken;
-        ClientRequestFilter authFilter2 = new ClientRequestFilter() {
-            @Override
-            public void filter(ClientRequestContext requestContext) throws IOException {
-                requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader2);
-            }
-        };
-        client = ClientBuilder.newBuilder().register(authFilter2).build();
-        tokenEndpoint = client.target(tokenEndpointUrl);
-        response = tokenEndpoint.request().get();
-
-        assertEquals(Status.FORBIDDEN.getStatusCode(), response.getStatus());
-
-        revokeGrant();
-        driver.navigate().to("http://localhost:8081/test-app/logout");
-        driver.navigate().to("http://localhost:8081/test-app");
-
-        assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
-    }
-
-    protected abstract void doAssertTokenRetrieval(String pageSource);
-
-    private UserModel assertSuccessfulAuthentication(IdentityProviderModel identityProviderModel, String username, String expectedEmail, boolean isProfileUpdateExpected) {
-        authenticateWithIdentityProvider(identityProviderModel, username, isProfileUpdateExpected);
-
-        // authenticated and redirected to app
-        assertTrue("Bad current URL " + this.driver.getCurrentUrl() + " and page source: " + this.driver.getPageSource(),
-                this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app"));
-
-        UserModel federatedUser = getFederatedUser();
-
-        assertNotNull(federatedUser);
-        assertNotNull(federatedUser.getCreatedTimestamp());
-        // test that timestamp is current with 10s tollerance
-        Assert.assertTrue((System.currentTimeMillis() - federatedUser.getCreatedTimestamp()) < 10000);
-
-        doAssertFederatedUser(federatedUser, identityProviderModel, expectedEmail, isProfileUpdateExpected);
-
-        brokerServerRule.stopSession(session, true);
-        session = brokerServerRule.startSession();
-
-        RealmModel realm = getRealm();
-
-        Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm);
-
-        assertEquals(1, federatedIdentities.size());
-
-        FederatedIdentityModel federatedIdentityModel = federatedIdentities.iterator().next();
-
-        assertEquals(getProviderId(), federatedIdentityModel.getIdentityProvider());
-        assertEquals(federatedUser.getUsername(), federatedIdentityModel.getIdentityProvider() + "." + federatedIdentityModel.getUserName());
-
-        driver.navigate().to("http://localhost:8081/test-app/logout");
-        driver.navigate().to("http://localhost:8081/test-app");
-
-        assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
-        return federatedUser;
-    }
-
-    private void authenticateWithIdentityProvider(IdentityProviderModel identityProviderModel, String username, boolean isProfileUpdateExpected) {
+    protected void authenticateWithIdentityProvider(IdentityProviderModel identityProviderModel, String username, boolean isProfileUpdateExpected) {
         loginIDP(username);
 
 
@@ -729,7 +203,7 @@ public abstract class AbstractIdentityProviderTest {
 
     }
 
-    private void loginIDP(String username) {
+    protected void loginIDP(String username) {
         driver.navigate().to("http://localhost:8081/test-app");
 
         assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
@@ -767,21 +241,27 @@ public abstract class AbstractIdentityProviderTest {
 
     protected abstract String getProviderId();
 
+
     protected IdentityProviderModel getIdentityProviderModel() {
         IdentityProviderModel identityProviderModel = getRealm().getIdentityProviderByAlias(getProviderId());
 
         assertNotNull(identityProviderModel);
 
-        identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_ON);
         identityProviderModel.setEnabled(true);
 
         return identityProviderModel;
     }
 
-    private RealmModel getRealm() {
-        return this.session.realms().getRealm("realm-with-broker");
+
+    protected RealmModel getRealm() {
+        return getRealm(this.session);
+    }
+
+    protected RealmModel getRealm(KeycloakSession session) {
+        return session.realms().getRealm("realm-with-broker");
     }
 
+
     protected void doAssertFederatedUser(UserModel federatedUser, IdentityProviderModel identityProviderModel, String expectedEmail, boolean isProfileUpdateExpected) {
         if (isProfileUpdateExpected) {
             String userFirstName = "New first";
@@ -797,19 +277,6 @@ public abstract class AbstractIdentityProviderTest {
         }
     }
 
-    private UserSessionStatus retrieveSessionStatus() {
-        UserSessionStatus sessionStatus = null;
-
-        try {
-            String pageSource = this.driver.getPageSource();
-
-            sessionStatus = JsonSerialization.readValue(pageSource.getBytes(), UserSessionStatus.class);
-        } catch (IOException ignore) {
-            ignore.printStackTrace();
-        }
-
-        return sessionStatus;
-    }
 
     private void removeTestUsers() {
         RealmModel realm = getRealm();
@@ -827,24 +294,58 @@ public abstract class AbstractIdentityProviderTest {
             }
         }
     }
-    
-    private String getVerificationEmailLink(MimeMessage message) throws IOException, MessagingException {
-    	Multipart multipart = (Multipart) message.getContent();
-    	
+
+
+    protected void setUpdateProfileFirstLogin(final String updateProfileFirstLogin) {
+        KeycloakModelUtils.runJobInTransaction(this.session.getKeycloakSessionFactory(), new KeycloakSessionTask() {
+
+            @Override
+            public void run(KeycloakSession session) {
+                RealmModel realm = getRealm(session);
+                setUpdateProfileFirstLogin(realm, updateProfileFirstLogin);
+            }
+
+        });
+    }
+
+    protected void setUpdateProfileFirstLogin(RealmModel realm, String updateProfileFirstLogin) {
+        AuthenticatorConfigModel reviewProfileConfig = realm.getAuthenticatorConfigByAlias(DefaultAuthenticationFlows.IDP_REVIEW_PROFILE_CONFIG_ALIAS);
+        reviewProfileConfig.getConfig().put(IdpReviewProfileAuthenticatorFactory.UPDATE_PROFILE_ON_FIRST_LOGIN, updateProfileFirstLogin);
+        realm.updateAuthenticatorConfig(reviewProfileConfig);
+    }
+
+
+    protected UserSessionStatusServlet.UserSessionStatus retrieveSessionStatus() {
+        UserSessionStatusServlet.UserSessionStatus sessionStatus = null;
+
+        try {
+            String pageSource = this.driver.getPageSource();
+
+            sessionStatus = JsonSerialization.readValue(pageSource.getBytes(), UserSessionStatusServlet.UserSessionStatus.class);
+        } catch (IOException ignore) {
+            ignore.printStackTrace();
+        }
+
+        return sessionStatus;
+    }
+
+    protected String getVerificationEmailLink(MimeMessage message) throws IOException, MessagingException {
+        Multipart multipart = (Multipart) message.getContent();
+
         final String textContentType = multipart.getBodyPart(0).getContentType();
-        
+
         assertEquals("text/plain; charset=UTF-8", textContentType);
-        
+
         final String textBody = (String) multipart.getBodyPart(0).getContent();
         final String textVerificationUrl = MailUtil.getLink(textBody);
-    	
+
         final String htmlContentType = multipart.getBodyPart(1).getContentType();
-        
+
         assertEquals("text/html; charset=UTF-8", htmlContentType);
-        
+
         final String htmlBody = (String) multipart.getBodyPart(1).getContent();
         final String htmlVerificationUrl = MailUtil.getLink(htmlBody);
-        
+
         assertEquals(htmlVerificationUrl, textVerificationUrl);
 
         return htmlVerificationUrl;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
new file mode 100644
index 0000000..1d2e5b6
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
@@ -0,0 +1,547 @@
+package org.keycloak.testsuite.broker;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Set;
+
+import javax.mail.MessagingException;
+import javax.mail.Multipart;
+import javax.mail.internet.MimeMessage;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientRequestFilter;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.Constants;
+import org.keycloak.models.FederatedIdentityModel;
+import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.representations.idm.IdentityProviderRepresentation;
+import org.keycloak.services.Urls;
+import org.keycloak.testsuite.MailUtil;
+import org.keycloak.testsuite.broker.util.UserSessionStatusServlet;
+import org.openqa.selenium.By;
+import org.openqa.selenium.NoSuchElementException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author pedroigor
+ */
+public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdentityProviderTest {
+
+    @Test
+    public void testSuccessfulAuthentication() {
+        IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+        setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_ON);
+
+        UserModel user = assertSuccessfulAuthentication(identityProviderModel, "test-user", "new@email.com", true);
+        Assert.assertEquals("617-666-7777", user.getFirstAttribute("mobile"));
+    }
+
+    @Test
+    public void testSuccessfulAuthenticationUpdateProfileOnMissing_nothingMissing() {
+        IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+        setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_MISSING);
+
+        assertSuccessfulAuthentication(identityProviderModel, "test-user", "test-user@localhost", false);
+    }
+
+    @Test
+    public void testSuccessfulAuthenticationUpdateProfileOnMissing_missingEmail() {
+        IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+        setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_MISSING);
+
+        assertSuccessfulAuthentication(identityProviderModel, "test-user-noemail", "new@email.com", true);
+    }
+
+    @Test
+    public void testSuccessfulAuthenticationWithoutUpdateProfile() {
+        IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+        setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
+
+        assertSuccessfulAuthentication(identityProviderModel, "test-user", "test-user@localhost", false);
+    }
+
+    /**
+     * Test that verify email action is performed if email is provided and email trust is not enabled for the provider
+     *
+     * @throws MessagingException
+     * @throws IOException
+     */
+    @Test
+    public void testSuccessfulAuthenticationWithoutUpdateProfile_emailProvided_emailVerifyEnabled() throws IOException, MessagingException {
+        getRealm().setVerifyEmail(true);
+        brokerServerRule.stopSession(this.session, true);
+        this.session = brokerServerRule.startSession();
+
+        IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+        try {
+            setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
+            identityProviderModel.setTrustEmail(false);
+
+            UserModel federatedUser = assertSuccessfulAuthenticationWithEmailVerification(identityProviderModel, "test-user", "test-user@localhost", false);
+
+            // email is verified now
+            assertFalse(federatedUser.getRequiredActions().contains(UserModel.RequiredAction.VERIFY_EMAIL.name()));
+
+        } finally {
+            getRealm().setVerifyEmail(false);
+        }
+    }
+
+    private UserModel assertSuccessfulAuthenticationWithEmailVerification(IdentityProviderModel identityProviderModel, String username, String expectedEmail,
+                                                                          boolean isProfileUpdateExpected)
+            throws IOException, MessagingException {
+        authenticateWithIdentityProvider(identityProviderModel, username, isProfileUpdateExpected);
+
+        // verify email is sent
+        Assert.assertTrue(verifyEmailPage.isCurrent());
+
+        // read email to take verification link from
+        Assert.assertEquals(1, greenMail.getReceivedMessages().length);
+        MimeMessage message = greenMail.getReceivedMessages()[0];
+        String verificationUrl = getVerificationEmailLink(message);
+
+        driver.navigate().to(verificationUrl.trim());
+
+        // authenticated and redirected to app
+        assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app"));
+
+        UserModel federatedUser = getFederatedUser();
+
+        assertNotNull(federatedUser);
+
+        doAssertFederatedUser(federatedUser, identityProviderModel, expectedEmail, isProfileUpdateExpected);
+
+        brokerServerRule.stopSession(session, true);
+        session = brokerServerRule.startSession();
+
+        RealmModel realm = getRealm();
+
+        Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm);
+
+        assertEquals(1, federatedIdentities.size());
+
+        FederatedIdentityModel federatedIdentityModel = federatedIdentities.iterator().next();
+
+        assertEquals(getProviderId(), federatedIdentityModel.getIdentityProvider());
+        assertEquals(federatedUser.getUsername(), federatedIdentityModel.getIdentityProvider() + "." + federatedIdentityModel.getUserName());
+
+        driver.navigate().to("http://localhost:8081/test-app/logout");
+        driver.navigate().to("http://localhost:8081/test-app");
+
+        assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
+        return federatedUser;
+    }
+
+    /**
+     * Test for KEYCLOAK-1053 - verify email action is not performed if email is not provided, login is normal, but action stays in set to be performed later
+     */
+    @Test
+    public void testSuccessfulAuthenticationWithoutUpdateProfile_emailNotProvided_emailVerifyEnabled() {
+        getRealm().setVerifyEmail(true);
+        brokerServerRule.stopSession(this.session, true);
+        this.session = brokerServerRule.startSession();
+
+        try {
+            IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+            setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
+
+            UserModel federatedUser = assertSuccessfulAuthentication(identityProviderModel, "test-user-noemail", null, false);
+
+            assertTrue(federatedUser.getRequiredActions().contains(UserModel.RequiredAction.VERIFY_EMAIL.name()));
+
+        } finally {
+            getRealm().setVerifyEmail(false);
+        }
+    }
+
+    /**
+     * Test for KEYCLOAK-1372 - verify email action is not performed if email is provided but email trust is enabled for the provider
+     */
+    @Test
+    public void testSuccessfulAuthenticationWithoutUpdateProfile_emailProvided_emailVerifyEnabled_emailTrustEnabled() {
+        getRealm().setVerifyEmail(true);
+        setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
+        brokerServerRule.stopSession(this.session, true);
+        this.session = brokerServerRule.startSession();
+
+        IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+        try {
+            identityProviderModel.setTrustEmail(true);
+
+            UserModel federatedUser = assertSuccessfulAuthentication(identityProviderModel, "test-user", "test-user@localhost", false);
+
+            assertFalse(federatedUser.getRequiredActions().contains(UserModel.RequiredAction.VERIFY_EMAIL.name()));
+
+        } finally {
+            identityProviderModel.setTrustEmail(false);
+            getRealm().setVerifyEmail(false);
+        }
+    }
+
+    /**
+     * Test for KEYCLOAK-1372 - verify email action is performed if email is provided and email trust is enabled for the provider, but email is changed on First login update profile page
+     *
+     * @throws MessagingException
+     * @throws IOException
+     */
+    @Test
+    public void testSuccessfulAuthentication_emailTrustEnabled_emailVerifyEnabled_emailUpdatedOnFirstLogin() throws IOException, MessagingException {
+        getRealm().setVerifyEmail(true);
+        brokerServerRule.stopSession(this.session, true);
+        this.session = brokerServerRule.startSession();
+
+        IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+        try {
+            setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_ON);
+            identityProviderModel.setTrustEmail(true);
+
+            UserModel user = assertSuccessfulAuthenticationWithEmailVerification(identityProviderModel, "test-user", "new@email.com", true);
+            Assert.assertEquals("617-666-7777", user.getFirstAttribute("mobile"));
+        } finally {
+            identityProviderModel.setTrustEmail(false);
+            getRealm().setVerifyEmail(false);
+        }
+    }
+
+    @Test
+    public void testSuccessfulAuthenticationWithoutUpdateProfile_newUser_emailAsUsername() {
+
+        getRealm().setRegistrationEmailAsUsername(true);
+        brokerServerRule.stopSession(this.session, true);
+        this.session = brokerServerRule.startSession();
+
+        try {
+            IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+            setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
+
+            authenticateWithIdentityProvider(identityProviderModel, "test-user", false);
+
+            // authenticated and redirected to app
+            assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app"));
+
+            brokerServerRule.stopSession(session, true);
+            session = brokerServerRule.startSession();
+
+            // check correct user is created with email as username and bound to correct federated identity
+            RealmModel realm = getRealm();
+
+            UserModel federatedUser = session.users().getUserByUsername("test-user@localhost", realm);
+
+            assertNotNull(federatedUser);
+
+            assertEquals("test-user@localhost", federatedUser.getUsername());
+
+            doAssertFederatedUser(federatedUser, identityProviderModel, "test-user@localhost", false);
+
+            Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm);
+
+            assertEquals(1, federatedIdentities.size());
+
+            FederatedIdentityModel federatedIdentityModel = federatedIdentities.iterator().next();
+
+            assertEquals(getProviderId(), federatedIdentityModel.getIdentityProvider());
+
+            driver.navigate().to("http://localhost:8081/test-app/logout");
+            driver.navigate().to("http://localhost:8081/test-app");
+
+            assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
+
+        } finally {
+            getRealm().setRegistrationEmailAsUsername(false);
+        }
+    }
+
+    @Test
+    public void testSuccessfulAuthenticationWithoutUpdateProfile_newUser_emailAsUsername_emailNotProvided() {
+
+        getRealm().setRegistrationEmailAsUsername(true);
+        brokerServerRule.stopSession(this.session, true);
+        this.session = brokerServerRule.startSession();
+
+        try {
+            IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+            setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
+
+            authenticateWithIdentityProvider(identityProviderModel, "test-user-noemail", false);
+
+            brokerServerRule.stopSession(session, true);
+            session = brokerServerRule.startSession();
+
+            // check correct user is created with username from provider as email is not available
+            RealmModel realm = getRealm();
+            UserModel federatedUser = getFederatedUser();
+            assertNotNull(federatedUser);
+
+            doAssertFederatedUserNoEmail(federatedUser);
+
+            Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm);
+
+            assertEquals(1, federatedIdentities.size());
+
+            FederatedIdentityModel federatedIdentityModel = federatedIdentities.iterator().next();
+
+            assertEquals(getProviderId(), federatedIdentityModel.getIdentityProvider());
+            revokeGrant();
+
+            driver.navigate().to("http://localhost:8081/test-app/logout");
+            driver.navigate().to("http://localhost:8081/test-app");
+
+            assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
+
+        } finally {
+            getRealm().setRegistrationEmailAsUsername(false);
+        }
+    }
+
+    @Test
+    public void testDisabled() {
+        IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+
+        identityProviderModel.setEnabled(false);
+
+        this.driver.navigate().to("http://localhost:8081/test-app/");
+
+        assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
+
+        try {
+            this.driver.findElement(By.className(getProviderId()));
+            fail("Provider [" + getProviderId() + "] not disabled.");
+        } catch (NoSuchElementException nsee) {
+
+        }
+    }
+
+    @Test
+    public void testProviderOnLoginPage() {
+        // Provider button is available on login page
+        this.driver.navigate().to("http://localhost:8081/test-app/");
+        assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
+        loginPage.findSocialButton(getProviderId());
+    }
+
+    @Test
+    public void testAccountManagementLinkIdentity() {
+        // Login as pedroigor to account management
+        accountFederatedIdentityPage.realm("realm-with-broker");
+        accountFederatedIdentityPage.open();
+        assertTrue(driver.getTitle().equals("Log in to realm-with-broker"));
+        loginPage.login("pedroigor", "password");
+        assertTrue(accountFederatedIdentityPage.isCurrent());
+
+        // Link my "pedroigor" identity with "test-user" from brokered Keycloak
+        IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+        setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_ON);
+        accountFederatedIdentityPage.clickAddProvider(identityProviderModel.getAlias());
+
+        assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/"));
+        this.loginPage.login("test-user", "password");
+        doAfterProviderAuthentication();
+
+        // Assert identity linked in account management
+        assertTrue(accountFederatedIdentityPage.isCurrent());
+        assertTrue(driver.getPageSource().contains("id=\"remove-" + identityProviderModel.getAlias() + "\""));
+
+        // Revoke grant in account mgmt
+        revokeGrant();
+
+        // Logout from account management
+        accountFederatedIdentityPage.logout();
+        assertTrue(driver.getTitle().equals("Log in to realm-with-broker"));
+
+        // Assert I am logged immediately to account management due to previously linked "test-user" identity
+        loginPage.clickSocial(identityProviderModel.getAlias());
+        doAfterProviderAuthentication();
+        assertTrue(accountFederatedIdentityPage.isCurrent());
+        assertTrue(driver.getPageSource().contains("id=\"remove-" + identityProviderModel.getAlias() + "\""));
+
+        // Unlink my "test-user"
+        accountFederatedIdentityPage.clickRemoveProvider(identityProviderModel.getAlias());
+        assertTrue(driver.getPageSource().contains("id=\"add-" + identityProviderModel.getAlias() + "\""));
+
+        // Revoke grant in account mgmt
+        revokeGrant();
+
+        // Logout from account management
+        System.out.println("*** logout from account management");
+        accountFederatedIdentityPage.logout();
+        assertTrue(driver.getTitle().equals("Log in to realm-with-broker"));
+        assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
+
+        // Try to login. Previous link is not valid anymore, so now it should try to register new user
+        this.loginPage.clickSocial(identityProviderModel.getAlias());
+        this.loginPage.login("test-user", "password");
+        doAfterProviderAuthentication();
+        this.updateProfilePage.assertCurrent();
+    }
+
+
+    // KEYCLOAK-1822
+    @Test
+    public void testAccountManagementLinkedIdentityAlreadyExists() {
+        // Login as "test-user" through broker
+        IdentityProviderModel identityProvider = getIdentityProviderModel();
+        assertSuccessfulAuthentication(identityProvider, "test-user", "test-user@localhost", false);
+
+        // Login as pedroigor to account management
+        accountFederatedIdentityPage.realm("realm-with-broker");
+        accountFederatedIdentityPage.open();
+        assertTrue(driver.getTitle().equals("Log in to realm-with-broker"));
+        loginPage.login("pedroigor", "password");
+        assertTrue(accountFederatedIdentityPage.isCurrent());
+
+        // Try to link my "pedroigor" identity with "test-user" from brokered Keycloak.
+        accountFederatedIdentityPage.clickAddProvider(identityProvider.getAlias());
+
+        assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/"));
+        this.loginPage.login("test-user", "password");
+        doAfterProviderAuthentication();
+
+        // Error is displayed in account management because federated identity"test-user" already linked to local account "test-user"
+        assertTrue(accountFederatedIdentityPage.isCurrent());
+        assertEquals("Federated identity returned by " + getProviderId() + " is already linked to another user.", accountFederatedIdentityPage.getError());
+    }
+
+
+    @Test(expected = NoSuchElementException.class)
+    public void testIdentityProviderNotAllowed() {
+        this.driver.navigate().to("http://localhost:8081/test-app/");
+
+        assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
+
+        driver.findElement(By.className("model-oidc-idp"));
+    }
+
+    protected void configureClientRetrieveToken(String clientId) {
+        RealmModel realm = getRealm();
+        RoleModel readTokenRole = realm.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID).getRole(Constants.READ_TOKEN_ROLE);
+        ClientModel client = realm.getClientByClientId(clientId);
+        if (!client.hasScope(readTokenRole)) client.addScopeMapping(readTokenRole);
+
+        brokerServerRule.stopSession(session, true);
+        session = brokerServerRule.startSession();
+
+    }
+
+    protected void configureUserRetrieveToken(String username) {
+        RealmModel realm = getRealm();
+        UserModel user = session.users().getUserByUsername(username, realm);
+        RoleModel readTokenRole = realm.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID).getRole(Constants.READ_TOKEN_ROLE);
+        if (user != null && !user.hasRole(readTokenRole)) {
+            user.grantRole(readTokenRole);
+        }
+        brokerServerRule.stopSession(session, true);
+        session = brokerServerRule.startSession();
+
+    }
+
+    protected void unconfigureClientRetrieveToken(String clientId) {
+        RealmModel realm = getRealm();
+        RoleModel readTokenRole = realm.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID).getRole(Constants.READ_TOKEN_ROLE);
+        ClientModel client = realm.getClientByClientId(clientId);
+        if (client.hasScope(readTokenRole)) client.deleteScopeMapping(readTokenRole);
+
+        brokerServerRule.stopSession(session, true);
+        session = brokerServerRule.startSession();
+
+    }
+
+    protected void unconfigureUserRetrieveToken(String username) {
+        RealmModel realm = getRealm();
+        UserModel user = session.users().getUserByUsername(username, realm);
+        RoleModel readTokenRole = realm.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID).getRole(Constants.READ_TOKEN_ROLE);
+        if (user != null && user.hasRole(readTokenRole)) {
+            user.deleteRoleMapping(readTokenRole);
+        }
+        brokerServerRule.stopSession(session, true);
+        session = brokerServerRule.startSession();
+
+    }
+
+    @Test
+    public void testTokenStorageAndRetrievalByApplication() {
+        setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_ON);
+        IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+
+        identityProviderModel.setStoreToken(true);
+
+        authenticateWithIdentityProvider(identityProviderModel, "test-user", true);
+
+        UserModel federatedUser = getFederatedUser();
+        RealmModel realm = getRealm();
+        Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm);
+
+        assertFalse(federatedIdentities.isEmpty());
+        assertEquals(1, federatedIdentities.size());
+
+        FederatedIdentityModel identityModel = federatedIdentities.iterator().next();
+
+        assertNotNull(identityModel.getToken());
+
+        UserSessionStatusServlet.UserSessionStatus userSessionStatus = retrieveSessionStatus();
+        String accessToken = userSessionStatus.getAccessTokenString();
+        URI tokenEndpointUrl = Urls.identityProviderRetrieveToken(BASE_URI, getProviderId(), realm.getName());
+        final String authHeader = "Bearer " + accessToken;
+        ClientRequestFilter authFilter = new ClientRequestFilter() {
+            @Override
+            public void filter(ClientRequestContext requestContext) throws IOException {
+                requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader);
+            }
+        };
+        Client client = ClientBuilder.newBuilder().register(authFilter).build();
+        WebTarget tokenEndpoint = client.target(tokenEndpointUrl);
+        Response response = tokenEndpoint.request().get();
+        assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
+        assertNotNull(response.readEntity(String.class));
+        revokeGrant();
+
+
+        driver.navigate().to("http://localhost:8081/test-app/logout");
+        String currentUrl = this.driver.getCurrentUrl();
+        System.out.println("after logout currentUrl: " + currentUrl);
+        assertTrue(currentUrl.startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
+
+        unconfigureUserRetrieveToken(getProviderId() + ".test-user");
+        loginIDP("test-user");
+        //authenticateWithIdentityProvider(identityProviderModel, "test-user");
+        assertEquals("http://localhost:8081/test-app", driver.getCurrentUrl());
+
+        userSessionStatus = retrieveSessionStatus();
+        accessToken = userSessionStatus.getAccessTokenString();
+        final String authHeader2 = "Bearer " + accessToken;
+        ClientRequestFilter authFilter2 = new ClientRequestFilter() {
+            @Override
+            public void filter(ClientRequestContext requestContext) throws IOException {
+                requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader2);
+            }
+        };
+        client = ClientBuilder.newBuilder().register(authFilter2).build();
+        tokenEndpoint = client.target(tokenEndpointUrl);
+        response = tokenEndpoint.request().get();
+
+        assertEquals(Response.Status.FORBIDDEN.getStatusCode(), response.getStatus());
+
+        revokeGrant();
+        driver.navigate().to("http://localhost:8081/test-app/logout");
+        driver.navigate().to("http://localhost:8081/test-app");
+
+        assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
+    }
+
+    protected abstract void doAssertTokenRetrieval(String pageSource);
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java
index 8914e7c..8e23f11 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java
@@ -30,9 +30,9 @@ import org.keycloak.broker.oidc.OIDCIdentityProviderFactory;
 import org.keycloak.broker.saml.SAMLIdentityProvider;
 import org.keycloak.broker.saml.SAMLIdentityProviderConfig;
 import org.keycloak.broker.saml.SAMLIdentityProviderFactory;
-import org.keycloak.models.ClientModel;
 import org.keycloak.models.IdentityProviderModel;
 import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.DefaultAuthenticationFlows;
 import org.keycloak.representations.idm.IdentityProviderRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.social.facebook.FacebookIdentityProvider;
@@ -63,7 +63,7 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
     public void testInstallation() throws Exception {
         RealmModel realm = installTestRealm();
 
-        assertIdentityProviderConfig(realm.getIdentityProviders());
+        assertIdentityProviderConfig(realm, realm.getIdentityProviders());
 
         assertTrue(realm.isIdentityFederationEnabled());
         this.realmManager.removeRealm(realm);
@@ -81,10 +81,10 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
 
         identityProviderModel.getConfig().put("config-added", "value-added");
         identityProviderModel.setEnabled(false);
-        identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_OFF);
         identityProviderModel.setTrustEmail(true);
         identityProviderModel.setStoreToken(true);
         identityProviderModel.setAuthenticateByDefault(true);
+        identityProviderModel.setFirstBrokerLoginFlowId(realm.getBrowserFlow().getId());
 
         realm.updateIdentityProvider(identityProviderModel);
 
@@ -96,14 +96,13 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
 
         assertEquals("value-added", identityProviderModel.getConfig().get("config-added"));
         assertFalse(identityProviderModel.isEnabled());
-        assertEquals(IdentityProviderRepresentation.UPFLM_OFF, identityProviderModel.getUpdateProfileFirstLoginMode());
         assertTrue(identityProviderModel.isTrustEmail());
         assertTrue(identityProviderModel.isStoreToken());
         assertTrue(identityProviderModel.isAuthenticateByDefault());
+        assertEquals(identityProviderModel.getFirstBrokerLoginFlowId(), realm.getBrowserFlow().getId());
 
         identityProviderModel.getConfig().remove("config-added");
         identityProviderModel.setEnabled(true);
-        identityProviderModel.setUpdateProfileFirstLoginMode(IdentityProviderRepresentation.UPFLM_MISSING);
         identityProviderModel.setTrustEmail(false);
         identityProviderModel.setAuthenticateByDefault(false);
 
@@ -116,13 +115,12 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
 
         assertFalse(identityProviderModel.getConfig().containsKey("config-added"));
         assertTrue(identityProviderModel.isEnabled());
-        assertEquals(IdentityProviderRepresentation.UPFLM_MISSING, identityProviderModel.getUpdateProfileFirstLoginMode());
         assertFalse(identityProviderModel.isTrustEmail());
         assertFalse(identityProviderModel.isAuthenticateByDefault());
         this.realmManager.removeRealm(realm);
     }
 
-    private void assertIdentityProviderConfig(List<IdentityProviderModel> identityProviders) {
+    private void assertIdentityProviderConfig(RealmModel realm, List<IdentityProviderModel> identityProviders) {
         assertFalse(identityProviders.isEmpty());
 
         Set<String> checkedProviders = new HashSet<String>(getExpectedProviders());
@@ -138,9 +136,9 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
                 } else if (OIDCIdentityProviderFactory.PROVIDER_ID.equals(providerId)) {
                     assertOidcIdentityProviderConfig(identityProvider);
                 } else if (FacebookIdentityProviderFactory.PROVIDER_ID.equals(providerId)) {
-                    assertFacebookIdentityProviderConfig(identityProvider);
+                    assertFacebookIdentityProviderConfig(realm, identityProvider);
                 } else if (GitHubIdentityProviderFactory.PROVIDER_ID.equals(providerId)) {
-                    assertGitHubIdentityProviderConfig(identityProvider);
+                    assertGitHubIdentityProviderConfig(realm, identityProvider);
                 } else if (TwitterIdentityProviderFactory.PROVIDER_ID.equals(providerId)) {
                     assertTwitterIdentityProviderConfig(identityProvider);
                 } else if (LinkedInIdentityProviderFactory.PROVIDER_ID.equals(providerId)) {
@@ -165,7 +163,6 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
         assertEquals("model-google", config.getAlias());
         assertEquals(GoogleIdentityProviderFactory.PROVIDER_ID, config.getProviderId());
         assertEquals(true, config.isEnabled());
-        assertEquals(IdentityProviderRepresentation.UPFLM_ON, config.getUpdateProfileFirstLoginMode());
         assertEquals(true, config.isTrustEmail());
         assertEquals(false, config.isAuthenticateByDefault());
         assertEquals(true, config.isStoreToken());
@@ -184,7 +181,6 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
         assertEquals("model-saml-signed-idp", config.getAlias());
         assertEquals(SAMLIdentityProviderFactory.PROVIDER_ID, config.getProviderId());
         assertEquals(true, config.isEnabled());
-        assertEquals(IdentityProviderRepresentation.UPFLM_ON, config.getUpdateProfileFirstLoginMode());
         assertEquals(false, config.isAuthenticateByDefault());
         assertEquals(false, config.isTrustEmail());
         assertEquals(false, config.isStoreToken());
@@ -205,7 +201,6 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
         assertEquals("model-oidc-idp", config.getAlias());
         assertEquals(OIDCIdentityProviderFactory.PROVIDER_ID, config.getProviderId());
         assertEquals(false, config.isEnabled());
-        assertEquals(IdentityProviderRepresentation.UPFLM_OFF, config.getUpdateProfileFirstLoginMode());
         assertEquals(false, config.isTrustEmail());
         assertEquals(false, config.isAuthenticateByDefault());
         assertEquals(false, config.isStoreToken());
@@ -213,37 +208,37 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
         assertEquals("clientSecret", config.getClientSecret());
     }
 
-    private void assertFacebookIdentityProviderConfig(IdentityProviderModel identityProvider) {
+    private void assertFacebookIdentityProviderConfig(RealmModel realm, IdentityProviderModel identityProvider) {
         FacebookIdentityProvider facebookIdentityProvider = new FacebookIdentityProviderFactory().create(identityProvider);
         OAuth2IdentityProviderConfig config = facebookIdentityProvider.getConfig();
 
         assertEquals("model-facebook", config.getAlias());
         assertEquals(FacebookIdentityProviderFactory.PROVIDER_ID, config.getProviderId());
         assertEquals(true, config.isEnabled());
-        assertEquals(IdentityProviderRepresentation.UPFLM_OFF, config.getUpdateProfileFirstLoginMode());
         assertEquals(false, config.isTrustEmail());
         assertEquals(false, config.isAuthenticateByDefault());
         assertEquals(false, config.isStoreToken());
         assertEquals("clientId", config.getClientId());
         assertEquals("clientSecret", config.getClientSecret());
+        assertEquals(realm.getBrowserFlow().getId(), identityProvider.getFirstBrokerLoginFlowId());
         assertEquals(FacebookIdentityProvider.AUTH_URL, config.getAuthorizationUrl());
         assertEquals(FacebookIdentityProvider.TOKEN_URL, config.getTokenUrl());
         assertEquals(FacebookIdentityProvider.PROFILE_URL, config.getUserInfoUrl());
     }
 
-    private void assertGitHubIdentityProviderConfig(IdentityProviderModel identityProvider) {
+    private void assertGitHubIdentityProviderConfig(RealmModel realm, IdentityProviderModel identityProvider) {
         GitHubIdentityProvider gitHubIdentityProvider = new GitHubIdentityProviderFactory().create(identityProvider);
         OAuth2IdentityProviderConfig config = gitHubIdentityProvider.getConfig();
 
         assertEquals("model-github", config.getAlias());
         assertEquals(GitHubIdentityProviderFactory.PROVIDER_ID, config.getProviderId());
         assertEquals(true, config.isEnabled());
-        assertEquals(IdentityProviderRepresentation.UPFLM_ON, config.getUpdateProfileFirstLoginMode());
         assertEquals(false, config.isTrustEmail());
         assertEquals(false, config.isAuthenticateByDefault());
         assertEquals(false, config.isStoreToken());
         assertEquals("clientId", config.getClientId());
         assertEquals("clientSecret", config.getClientSecret());
+        assertEquals(realm.getFlowByAlias(DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW).getId(), identityProvider.getFirstBrokerLoginFlowId());
         assertEquals(GitHubIdentityProvider.AUTH_URL, config.getAuthorizationUrl());
         assertEquals(GitHubIdentityProvider.TOKEN_URL, config.getTokenUrl());
         assertEquals(GitHubIdentityProvider.PROFILE_URL, config.getUserInfoUrl());
@@ -254,17 +249,16 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
         OAuth2IdentityProviderConfig config = liIdentityProvider.getConfig();
 
         assertEquals("model-linkedin", config.getAlias());
-      assertEquals(LinkedInIdentityProviderFactory.PROVIDER_ID, config.getProviderId());
-      assertEquals(true, config.isEnabled());
-        assertEquals(IdentityProviderRepresentation.UPFLM_MISSING, config.getUpdateProfileFirstLoginMode());
+        assertEquals(LinkedInIdentityProviderFactory.PROVIDER_ID, config.getProviderId());
+        assertEquals(true, config.isEnabled());
         assertEquals(false, config.isTrustEmail());
-      assertEquals(false, config.isAuthenticateByDefault());
-      assertEquals(false, config.isStoreToken());
-      assertEquals("clientId", config.getClientId());
-      assertEquals("clientSecret", config.getClientSecret());
-      assertEquals(LinkedInIdentityProvider.AUTH_URL, config.getAuthorizationUrl());
-      assertEquals(LinkedInIdentityProvider.TOKEN_URL, config.getTokenUrl());
-      assertEquals(LinkedInIdentityProvider.PROFILE_URL, config.getUserInfoUrl());
+        assertEquals(false, config.isAuthenticateByDefault());
+        assertEquals(false, config.isStoreToken());
+        assertEquals("clientId", config.getClientId());
+        assertEquals("clientSecret", config.getClientSecret());
+        assertEquals(LinkedInIdentityProvider.AUTH_URL, config.getAuthorizationUrl());
+        assertEquals(LinkedInIdentityProvider.TOKEN_URL, config.getTokenUrl());
+        assertEquals(LinkedInIdentityProvider.PROFILE_URL, config.getUserInfoUrl());
     }
 
     private void assertStackoverflowIdentityProviderConfig(IdentityProviderModel identityProvider) {
@@ -274,7 +268,6 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
         assertEquals("model-stackoverflow", config.getAlias());
         assertEquals(StackoverflowIdentityProviderFactory.PROVIDER_ID, config.getProviderId());
         assertEquals(true, config.isEnabled());
-        assertEquals(IdentityProviderRepresentation.UPFLM_OFF, config.getUpdateProfileFirstLoginMode());
         assertEquals(false, config.isTrustEmail());
         assertEquals(false, config.isAuthenticateByDefault());
         assertEquals(false, config.isStoreToken());
@@ -293,7 +286,6 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
         assertEquals("model-twitter", config.getAlias());
         assertEquals(TwitterIdentityProviderFactory.PROVIDER_ID, config.getProviderId());
         assertEquals(true, config.isEnabled());
-        assertEquals(IdentityProviderRepresentation.UPFLM_OFF, config.getUpdateProfileFirstLoginMode());
         assertEquals(false, config.isTrustEmail());
         assertEquals(false, config.isAuthenticateByDefault());
         assertEquals(true, config.isStoreToken());
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCFirstBrokerLoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCFirstBrokerLoginTest.java
new file mode 100644
index 0000000..d689363
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCFirstBrokerLoginTest.java
@@ -0,0 +1,145 @@
+package org.keycloak.testsuite.broker;
+
+import java.util.Set;
+
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.keycloak.authentication.authenticators.broker.IdpEmailVerificationAuthenticatorFactory;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.FederatedIdentityModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.DefaultAuthenticationFlows;
+import org.keycloak.representations.idm.IdentityProviderRepresentation;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.KeycloakServer;
+import org.keycloak.testsuite.rule.AbstractKeycloakRule;
+import org.keycloak.testsuite.rule.KeycloakRule;
+import org.openqa.selenium.NoSuchElementException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class OIDCFirstBrokerLoginTest extends AbstractFirstBrokerLoginTest {
+
+    private static final int PORT = 8082;
+
+    @ClassRule
+    public static AbstractKeycloakRule samlServerRule = new AbstractKeycloakRule() {
+
+        @Override
+        protected void configureServer(KeycloakServer server) {
+            server.getConfig().setPort(PORT);
+        }
+
+        @Override
+        protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) {
+            server.importRealm(getClass().getResourceAsStream("/broker-test/test-broker-realm-with-kc-oidc.json"));
+            server.importRealm(getClass().getResourceAsStream("/broker-test/test-broker-realm-with-saml.json"));
+        }
+
+        @Override
+        protected String[] getTestRealms() {
+            return new String[] { "realm-with-oidc-identity-provider", "realm-with-saml-idp-basic" };
+        }
+    };
+
+    @Override
+    protected String getProviderId() {
+        return "kc-oidc-idp";
+    }
+
+
+    /**
+     * Tests that duplication is detected and user wants to link federatedIdentity with existing account. He will confirm link by reauthentication
+     * with different broker already linked to his account
+     */
+    @Test
+    public void testLinkAccountByReauthenticationWithDifferentBroker() throws Exception {
+        brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
+                setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_HANDLE_EXISTING_SUBFLOW,
+                        IdpEmailVerificationAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.DISABLED);
+
+                setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_OFF);
+            }
+
+        }, APP_REALM_ID);
+
+        // First link "pedroigor" user with SAML broker and logout
+        driver.navigate().to("http://localhost:8081/test-app");
+        this.loginPage.clickSocial("kc-saml-idp-basic");
+        assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/"));
+        Assert.assertEquals("Log in to realm-with-saml-idp-basic", this.driver.getTitle());
+        this.loginPage.login("pedroigor", "password");
+
+        this.idpConfirmLinkPage.assertCurrent();
+        Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
+        this.idpConfirmLinkPage.clickLinkAccount();
+
+        this.loginPage.login("password");
+        assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app"));
+        driver.navigate().to("http://localhost:8081/test-app/logout");
+
+
+        // login through OIDC broker now
+        loginIDP("pedroigor");
+
+        this.idpConfirmLinkPage.assertCurrent();
+        Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
+        this.idpConfirmLinkPage.clickLinkAccount();
+
+        // assert reauthentication with login page. On login page is link to kc-saml-idp-basic as user has it linked already
+        Assert.assertEquals("Log in to " + APP_REALM_ID, this.driver.getTitle());
+        Assert.assertEquals("Authenticate as pedroigor to link your account with " + getProviderId(), this.loginPage.getSuccessMessage());
+
+        try {
+            this.loginPage.findSocialButton(getProviderId());
+            Assert.fail("Not expected to see social button with " + getProviderId());
+        } catch (NoSuchElementException expected) {
+        }
+
+        // reauthenticate with SAML broker
+        this.loginPage.clickSocial("kc-saml-idp-basic");
+        Assert.assertEquals("Log in to realm-with-saml-idp-basic", this.driver.getTitle());
+        this.loginPage.login("pedroigor", "password");
+
+
+        // authenticated and redirected to app. User is linked with identity provider
+        assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app"));
+        UserModel federatedUser = getFederatedUser();
+
+        assertNotNull(federatedUser);
+        assertEquals("pedroigor", federatedUser.getUsername());
+        assertEquals("psilva@redhat.com", federatedUser.getEmail());
+
+        RealmModel realmWithBroker = getRealm();
+        Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realmWithBroker);
+        assertEquals(2, federatedIdentities.size());
+
+        for (FederatedIdentityModel link : federatedIdentities) {
+            Assert.assertEquals("pedroigor", link.getUserName());
+            Assert.assertTrue(link.getIdentityProvider().equals(getProviderId()) || link.getIdentityProvider().equals("kc-saml-idp-basic"));
+        }
+
+        brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
+                setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_HANDLE_EXISTING_SUBFLOW,
+                        IdpEmailVerificationAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.ALTERNATIVE);
+
+            }
+
+        }, APP_REALM_ID);
+    }
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java
index 0a7ee16..5bfdc1d 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java
@@ -26,7 +26,7 @@ import static org.junit.Assert.fail;
 /**
  * @author pedroigor
  */
-public class OIDCKeyCloakServerBrokerBasicTest extends AbstractIdentityProviderTest {
+public class OIDCKeyCloakServerBrokerBasicTest extends AbstractKeycloakIdentityProviderTest {
 
     private static final int PORT = 8082;
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLFirstBrokerLoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLFirstBrokerLoginTest.java
new file mode 100644
index 0000000..66692d4
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLFirstBrokerLoginTest.java
@@ -0,0 +1,40 @@
+package org.keycloak.testsuite.broker;
+
+import org.junit.ClassRule;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.KeycloakServer;
+import org.keycloak.testsuite.rule.AbstractKeycloakRule;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class SAMLFirstBrokerLoginTest extends AbstractFirstBrokerLoginTest {
+
+    private static final int PORT = 8082;
+
+    @ClassRule
+    public static AbstractKeycloakRule samlServerRule = new AbstractKeycloakRule() {
+
+        @Override
+        protected void configureServer(KeycloakServer server) {
+            server.getConfig().setPort(PORT);
+        }
+
+        @Override
+        protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) {
+            server.importRealm(getClass().getResourceAsStream("/broker-test/test-broker-realm-with-saml.json"));
+        }
+
+        @Override
+        protected String[] getTestRealms() {
+            return new String[] { "realm-with-saml-idp-basic" };
+        }
+    };
+
+    @Override
+    protected String getProviderId() {
+        return "kc-saml-idp-basic";
+    }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerBasicTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerBasicTest.java
index 7bbaa20..8be9dcc 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerBasicTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerBasicTest.java
@@ -24,7 +24,7 @@ import static org.junit.Assert.fail;
 /**
  * @author pedroigor
  */
-public class SAMLKeyCloakServerBrokerBasicTest extends AbstractIdentityProviderTest {
+public class SAMLKeyCloakServerBrokerBasicTest extends AbstractKeycloakIdentityProviderTest {
 
     @ClassRule
     public static AbstractKeycloakRule samlServerRule = new AbstractKeycloakRule() {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerWithSignatureTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerWithSignatureTest.java
index 249f2e0..197749b 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerWithSignatureTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerWithSignatureTest.java
@@ -24,7 +24,7 @@ import static org.junit.Assert.fail;
 /**
  * @author pedroigor
  */
-public class SAMLKeyCloakServerBrokerWithSignatureTest extends AbstractIdentityProviderTest {
+public class SAMLKeyCloakServerBrokerWithSignatureTest extends AbstractKeycloakIdentityProviderTest {
 
     @ClassRule
     public static AbstractKeycloakRule samlServerRule = new AbstractKeycloakRule() {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProvider.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProvider.java
index b501da9..51dbbd4 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProvider.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProvider.java
@@ -1,6 +1,7 @@
 package org.keycloak.testsuite;
 
 import org.keycloak.models.CredentialValidationOutput;
+import org.keycloak.models.GroupModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserCredentialModel;
@@ -68,6 +69,11 @@ public class DummyUserFederationProvider implements UserFederationProvider {
     }
 
     @Override
+    public void preRemove(RealmModel realm, GroupModel group) {
+
+    }
+
+    @Override
     public boolean isValid(RealmModel realm, UserModel local) {
         return false;
     }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java
index 1fc4fc2..2f89194 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java
@@ -163,8 +163,9 @@ public class ExportImportTest {
 
         testRealmExportImport();
 
-        // There should be 3 files in target directory (1 realm, 2 user, 1 version)
-        Assert.assertEquals(4, new File(targetDirPath).listFiles().length);
+        // There should be 3 files in target directory (1 realm, 3 user, 1 version)
+        File[] files = new File(targetDirPath).listFiles();
+        Assert.assertEquals(5, files.length);
     }
 
     @Test
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java
index ac858dd..4e52db0 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java
@@ -127,7 +127,34 @@ public abstract class AbstractKerberosTest {
 
         spnegoResponse.close();
         events.clear();
-     }
+    }
+
+    // KEYCLOAK-2102
+    @Test
+    public void spnegoCaseInsensitiveTest() throws Exception {
+        KeycloakRule keycloakRule = getKeycloakRule();
+        AssertEvents events = getAssertEvents();
+
+        Response spnegoResponse = spnegoLogin("MyDuke", "theduke");
+        Assert.assertEquals(302, spnegoResponse.getStatus());
+
+        events.expectLogin()
+                .client("kerberos-app")
+                .user(keycloakRule.getUser("test", "myduke").getId())
+                .detail(Details.REDIRECT_URI, KERBEROS_APP_URL)
+                        //.detail(Details.AUTH_METHOD, "spnego")
+                .detail(Details.USERNAME, "myduke")
+                .assertEvent();
+
+        String location = spnegoResponse.getLocation().toString();
+        driver.navigate().to(location);
+
+        String pageSource = driver.getPageSource();
+        Assert.assertTrue(pageSource.contains("Kerberos Test") && pageSource.contains("Kerberos servlet secured content"));
+
+        spnegoResponse.close();
+        events.clear();
+    }
 
 
     @Test
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
index 950c182..30d94fa 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
@@ -215,9 +215,10 @@ public class LoginTest {
             Assert.assertEquals("login-test", loginPage.getUsername());
             Assert.assertEquals("", loginPage.getPassword());
 
-            Assert.assertEquals("Account is disabled, contact admin.", loginPage.getError());
+            // KEYCLOAK-2024
+            Assert.assertEquals("Invalid username or password.", loginPage.getError());
 
-            events.expectLogin().user(userId).session((String) null).error("user_disabled")
+            events.expectLogin().user(userId).session((String) null).error("invalid_user_credentials")
                     .detail(Details.USERNAME, "login-test")
                     .removeDetail(Details.CONSENT)
                     .assertEvent();
@@ -250,6 +251,7 @@ public class LoginTest {
             Assert.assertEquals("login-test", loginPage.getUsername());
             Assert.assertEquals("", loginPage.getPassword());
 
+            // KEYCLOAK-2024
             Assert.assertEquals("Account is disabled, contact admin.", loginPage.getError());
 
             events.expectLogin().user(userId).session((String) null).error("user_disabled")
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTestStrategy.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTestStrategy.java
index 2c32d51..4791c2a 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTestStrategy.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTestStrategy.java
@@ -22,10 +22,12 @@ import org.keycloak.models.UserSessionModel;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
 import org.keycloak.protocol.oidc.TokenManager;
 import org.keycloak.protocol.saml.mappers.AttributeStatementHelper;
+import org.keycloak.protocol.saml.mappers.GroupMembershipMapper;
 import org.keycloak.protocol.saml.mappers.HardcodedAttributeMapper;
 import org.keycloak.protocol.saml.mappers.HardcodedRole;
 import org.keycloak.protocol.saml.mappers.RoleListMapper;
 import org.keycloak.protocol.saml.mappers.RoleNameMapper;
+import org.keycloak.protocol.saml.mappers.UserAttributeStatementMapper;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
@@ -53,8 +55,10 @@ import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Set;
 
 import static org.junit.Assert.assertEquals;
 
@@ -202,6 +206,40 @@ public class SamlAdapterTestStrategy  extends ExternalResource {
     }
 
     public void testAttributes() throws Exception {
+        keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                ClientModel app = appRealm.getClientByClientId(APP_SERVER_BASE_URL + "/employee2/");
+                app.addProtocolMapper(GroupMembershipMapper.create("groups", "group", null, null, true));
+                app.addProtocolMapper(UserAttributeStatementMapper.createAttributeMapper("topAttribute", "topAttribute", "topAttribute", "Basic", null, false, null));
+                app.addProtocolMapper(UserAttributeStatementMapper.createAttributeMapper("level2Attribute", "level2Attribute", "level2Attribute", "Basic", null, false, null));
+            }
+        }, "demo");
+        {
+            SendUsernameServlet.sentPrincipal = null;
+            SendUsernameServlet.checkRoles = null;
+            driver.navigate().to(APP_SERVER_BASE_URL + "/employee2/");
+            Assert.assertTrue(driver.getCurrentUrl().startsWith(AUTH_SERVER_URL + "/realms/demo/protocol/saml"));
+            List<String> requiredRoles = new LinkedList<>();
+            requiredRoles.add("manager");
+            requiredRoles.add("user");
+            SendUsernameServlet.checkRoles = requiredRoles;
+            loginPage.login("level2GroupUser", "password");
+            assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee2/");
+            SendUsernameServlet.checkRoles = null;
+            SamlPrincipal principal = (SamlPrincipal) SendUsernameServlet.sentPrincipal;
+            Assert.assertNotNull(principal);
+            assertEquals("level2@redhat.com", principal.getAttribute(X500SAMLProfileConstants.EMAIL.get()));
+            assertEquals("true", principal.getAttribute("topAttribute"));
+            assertEquals("true", principal.getAttribute("level2Attribute"));
+            List<String> groups = principal.getAttributes("group");
+            Assert.assertNotNull(groups);
+            Set<String> groupSet = new HashSet<>();
+            assertEquals("level2@redhat.com", principal.getFriendlyAttribute("email"));
+            driver.navigate().to(APP_SERVER_BASE_URL + "/employee2/?GLO=true");
+            checkLoggedOut(APP_SERVER_BASE_URL + "/employee2/");
+
+        }
         {
             SendUsernameServlet.sentPrincipal = null;
             SendUsernameServlet.checkRoles = null;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/KeycloakServer.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/KeycloakServer.java
index 78bc481..28f0915 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/KeycloakServer.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/KeycloakServer.java
@@ -161,12 +161,19 @@ public class KeycloakServer {
 
             if (!System.getProperties().containsKey("keycloak.theme.dir")) {
                 System.setProperty("keycloak.theme.dir", file(dir.getAbsolutePath(), "forms", "common-themes", "src", "main", "resources", "theme").getAbsolutePath());
+            } else {
+                String foo = System.getProperty("keycloak.theme.dir");
+                System.out.println(foo);
             }
 
             if (!System.getProperties().containsKey("keycloak.theme.cacheTemplates")) {
                 System.setProperty("keycloak.theme.cacheTemplates", "false");
             }
 
+            if (!System.getProperties().containsKey("keycloak.theme.cacheThemes")) {
+                System.setProperty("keycloak.theme.cacheThemes", "false");
+            }
+
             if (!System.getProperties().containsKey("keycloak.theme.staticMaxAge")) {
                 System.setProperty("keycloak.theme.staticMaxAge", "-1");
             }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java
index 4e771df..8c84581 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java
@@ -4,6 +4,7 @@ import org.junit.Assert;
 import org.junit.FixMethodOrder;
 import org.junit.Test;
 import org.junit.runners.MethodSorters;
+import org.keycloak.Config;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.FederatedIdentityModel;
 import org.keycloak.models.ModelDuplicateException;
@@ -146,11 +147,11 @@ public class AdapterTest extends AbstractModelTest {
         Assert.assertTrue(userProvider.validCredentials(realmModel, user, UserCredentialModel.password("geheim")));
         List<UserCredentialValueModel> creds = user.getCredentialsDirectly();
         Assert.assertEquals(creds.get(0).getHashIterations(), 1);
-        realmModel.setPasswordPolicy( new PasswordPolicy("hashIterations(200)"));
+        realmModel.setPasswordPolicy(new PasswordPolicy("hashIterations(200)"));
         Assert.assertTrue(userProvider.validCredentials(realmModel, user, UserCredentialModel.password("geheim")));
         creds = user.getCredentialsDirectly();
         Assert.assertEquals(creds.get(0).getHashIterations(), 200);
-        realmModel.setPasswordPolicy( new PasswordPolicy("hashIterations(1)"));
+        realmModel.setPasswordPolicy(new PasswordPolicy("hashIterations(1)"));
     }
 
     @Test
@@ -797,6 +798,22 @@ public class AdapterTest extends AbstractModelTest {
 
     }
 
+    // KEYCLOAK-2026
+    @Test
+    public void testMasterAdminClient() {
+        realmModel = realmManager.createRealm("foo-realm");
+        ClientModel masterAdminClient = realmModel.getMasterAdminClient();
+        Assert.assertEquals(Config.getAdminRealm(), masterAdminClient.getRealm().getId());
+
+        commit();
+
+        realmModel = realmManager.getRealmByName("foo-realm");
+        masterAdminClient = realmModel.getMasterAdminClient();
+        Assert.assertEquals(Config.getAdminRealm(), masterAdminClient.getRealm().getId());
+
+        realmManager.removeRealm(realmModel);
+    }
+
     private KeyPair generateKeypair() throws NoSuchAlgorithmException {
         return KeyPairGenerator.getInstance("RSA").generateKeyPair();
     }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/GroupTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/GroupTest.java
new file mode 100755
index 0000000..97250c4
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/GroupTest.java
@@ -0,0 +1,271 @@
+package org.keycloak.testsuite.model;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.admin.client.Keycloak;
+import org.keycloak.admin.client.resource.GroupResource;
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
+import org.keycloak.common.util.Time;
+import org.keycloak.events.Details;
+import org.keycloak.events.Errors;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.Constants;
+import org.keycloak.models.PasswordPolicy;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.protocol.oidc.mappers.GroupMembershipMapper;
+import org.keycloak.protocol.oidc.mappers.UserAttributeMapper;
+import org.keycloak.protocol.saml.mappers.AttributeStatementHelper;
+import org.keycloak.protocol.saml.mappers.HardcodedAttributeMapper;
+import org.keycloak.protocol.saml.mappers.HardcodedRole;
+import org.keycloak.protocol.saml.mappers.RoleListMapper;
+import org.keycloak.protocol.saml.mappers.RoleNameMapper;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.RefreshToken;
+import org.keycloak.representations.idm.GroupRepresentation;
+import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.services.managers.ClientManager;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.OAuthClient;
+import org.keycloak.testsuite.rule.KeycloakRule;
+import org.keycloak.testsuite.rule.WebResource;
+import org.keycloak.testsuite.rule.WebRule;
+import org.openqa.selenium.WebDriver;
+
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.core.Response;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class GroupTest {
+
+    @ClassRule
+    public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
+        @Override
+        public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+            ClientModel app = new ClientManager(manager).createClient(appRealm, "resource-owner");
+            app.setSecret("secret");
+
+            UserModel user = session.users().addUser(appRealm, "direct-login");
+            user.setEmail("direct-login@localhost");
+            user.setEnabled(true);
+
+
+            session.users().updateCredential(appRealm, user, UserCredentialModel.password("password"));
+            keycloak = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", Constants.ADMIN_CONSOLE_CLIENT_ID);
+        }
+    });
+
+    protected static Keycloak keycloak;
+
+    @Rule
+    public AssertEvents events = new AssertEvents(keycloakRule);
+
+    @Rule
+    public WebRule webRule = new WebRule(this);
+
+    @WebResource
+    protected WebDriver driver;
+
+    @WebResource
+    protected OAuthClient oauth;
+
+    @Test
+    public void createAndTestGroups() throws Exception {
+        RealmResource realm = keycloak.realms().realm("test");
+        {
+            RoleRepresentation groupRole = new RoleRepresentation();
+            groupRole.setName("topRole");
+            realm.roles().create(groupRole);
+        }
+        RoleRepresentation topRole = realm.roles().get("topRole").toRepresentation();
+        {
+            RoleRepresentation groupRole = new RoleRepresentation();
+            groupRole.setName("level2Role");
+            realm.roles().create(groupRole);
+        }
+        RoleRepresentation level2Role = realm.roles().get("level2Role").toRepresentation();
+        {
+            RoleRepresentation groupRole = new RoleRepresentation();
+            groupRole.setName("level3Role");
+            realm.roles().create(groupRole);
+        }
+        RoleRepresentation level3Role = realm.roles().get("level3Role").toRepresentation();
+
+
+        GroupRepresentation topGroup = new GroupRepresentation();
+        topGroup.setName("top");
+        Response response = realm.groups().add(topGroup);
+        response.close();
+        topGroup = realm.getGroupByPath("/top");
+        Assert.assertNotNull(topGroup);
+        List<RoleRepresentation> roles = new LinkedList<>();
+        roles.add(topRole);
+        realm.groups().group(topGroup.getId()).roles().realmLevel().add(roles);
+
+        GroupRepresentation level2Group = new GroupRepresentation();
+        level2Group.setName("level2");
+        response = realm.groups().group(topGroup.getId()).subGroup(level2Group);
+        response.close();
+        level2Group = realm.getGroupByPath("/top/level2");
+        Assert.assertNotNull(level2Group);
+        roles.clear();
+        roles.add(level2Role);
+        realm.groups().group(level2Group.getId()).roles().realmLevel().add(roles);
+
+        GroupRepresentation level3Group = new GroupRepresentation();
+        level3Group.setName("level3");
+        response = realm.groups().group(level2Group.getId()).subGroup(level3Group);
+        response.close();
+        level3Group = realm.getGroupByPath("/top/level2/level3");
+        Assert.assertNotNull(level3Group);
+        roles.clear();
+        roles.add(level3Role);
+        realm.groups().group(level3Group.getId()).roles().realmLevel().add(roles);
+
+        topGroup = realm.getGroupByPath("/top");
+        Assert.assertEquals(1, topGroup.getRealmRoles().size());
+        Assert.assertTrue(topGroup.getRealmRoles().contains("topRole"));
+        Assert.assertEquals(1, topGroup.getSubGroups().size());
+
+        level2Group = topGroup.getSubGroups().get(0);
+        Assert.assertEquals("level2", level2Group.getName());
+        Assert.assertEquals(1, level2Group.getRealmRoles().size());
+        Assert.assertTrue(level2Group.getRealmRoles().contains("level2Role"));
+        Assert.assertEquals(1, level2Group.getSubGroups().size());
+
+        level3Group = level2Group.getSubGroups().get(0);
+        Assert.assertEquals("level3", level3Group.getName());
+        Assert.assertEquals(1, level3Group.getRealmRoles().size());
+        Assert.assertTrue(level3Group.getRealmRoles().contains("level3Role"));
+
+        try {
+            GroupRepresentation notFound = realm.getGroupByPath("/notFound");
+            Assert.fail();
+        } catch (NotFoundException e) {
+
+        }
+        try {
+            GroupRepresentation notFound = realm.getGroupByPath("/top/notFound");
+            Assert.fail();
+        } catch (NotFoundException e) {
+
+        }
+
+        UserRepresentation user = realm.users().search("direct-login", -1, -1).get(0);
+        realm.users().get(user.getId()).joinGroup(level3Group.getId());
+        List<GroupRepresentation> membership = realm.users().get(user.getId()).groups();
+        Assert.assertEquals(1, membership.size());
+        Assert.assertEquals("level3", membership.get(0).getName());
+
+        AccessToken token = login("direct-login", "resource-owner", "secret", user.getId());
+        Assert.assertTrue(token.getRealmAccess().getRoles().contains("topRole"));
+        Assert.assertTrue(token.getRealmAccess().getRoles().contains("level2Role"));
+        Assert.assertTrue(token.getRealmAccess().getRoles().contains("level3Role"));
+
+        realm.addDefaultGroup(level3Group.getId());
+
+        List<GroupRepresentation> defaultGroups = realm.getDefaultGroups();
+        Assert.assertEquals(1, defaultGroups.size());
+        Assert.assertEquals(defaultGroups.get(0).getId(), level3Group.getId());
+
+        UserRepresentation newUser = new UserRepresentation();
+        newUser.setUsername("groupUser");
+        newUser.setEmail("group@group.com");
+        response = realm.users().create(newUser);
+        response.close();
+        newUser =  realm.users().search("groupUser", -1, -1).get(0);
+        membership = realm.users().get(newUser.getId()).groups();
+        Assert.assertEquals(1, membership.size());
+        Assert.assertEquals("level3", membership.get(0).getName());
+
+        realm.removeDefaultGroup(level3Group.getId());
+        defaultGroups = realm.getDefaultGroups();
+        Assert.assertEquals(0, defaultGroups.size());
+
+    }
+
+    @Test
+    public void testGroupMappers() throws Exception {
+        keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                ClientModel app = appRealm.getClientByClientId("test-app");
+                app.addProtocolMapper(GroupMembershipMapper.create("groups", "groups", false, null, true, true));
+                app.addProtocolMapper(UserAttributeMapper.createClaimMapper("topAttribute", "topAttribute", "topAttribute", ProviderConfigProperty.STRING_TYPE, false, null, true, true, false));
+                app.addProtocolMapper(UserAttributeMapper.createClaimMapper("level2Attribute", "level2Attribute", "level2Attribute", ProviderConfigProperty.STRING_TYPE, false, null, true, true, false));
+            }
+        }, "test");
+        RealmResource realm = keycloak.realms().realm("test");
+        {
+            UserRepresentation user = realm.users().search("topGroupUser", -1, -1).get(0);
+
+            AccessToken token = login(user.getUsername(), "test-app", "password", user.getId());
+            Assert.assertTrue(token.getRealmAccess().getRoles().contains("user"));
+            List<String> groups = (List<String>) token.getOtherClaims().get("groups");
+            Assert.assertNotNull(groups);
+            Assert.assertTrue(groups.size() == 1);
+            Assert.assertEquals("topGroup", groups.get(0));
+            Assert.assertEquals("true", token.getOtherClaims().get("topAttribute"));
+        }
+        {
+            UserRepresentation user = realm.users().search("level2GroupUser", -1, -1).get(0);
+
+            AccessToken token = login(user.getUsername(), "test-app", "password", user.getId());
+            Assert.assertTrue(token.getRealmAccess().getRoles().contains("user"));
+            Assert.assertTrue(token.getRealmAccess().getRoles().contains("admin"));
+            Assert.assertTrue(token.getResourceAccess("test-app").getRoles().contains("customer-user"));
+            List<String> groups = (List<String>) token.getOtherClaims().get("groups");
+            Assert.assertNotNull(groups);
+            Assert.assertTrue(groups.size() == 1);
+            Assert.assertEquals("level2group", groups.get(0));
+            Assert.assertEquals("true", token.getOtherClaims().get("topAttribute"));
+            Assert.assertEquals("true", token.getOtherClaims().get("level2Attribute"));
+        }
+
+    }
+
+    protected AccessToken login(String login, String clientId, String clientSecret, String userId) throws Exception {
+        oauth.clientId(clientId);
+
+        OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest(clientSecret, login, "password");
+
+        assertEquals(200, response.getStatusCode());
+
+        AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
+        RefreshToken refreshToken = oauth.verifyRefreshToken(response.getRefreshToken());
+
+        events.expectLogin()
+                .client(clientId)
+                .user(userId)
+                .session(accessToken.getSessionState())
+                .detail(Details.RESPONSE_TYPE, "token")
+                .detail(Details.TOKEN_ID, accessToken.getId())
+                .detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
+                .detail(Details.USERNAME, login)
+                .removeDetail(Details.CODE_ID)
+                .removeDetail(Details.REDIRECT_URI)
+                .removeDetail(Details.CONSENT)
+                .assertEvent();
+        return accessToken;
+    }
+
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountFederatedIdentityPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountFederatedIdentityPage.java
index 4c02798..54a5cbb 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountFederatedIdentityPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountFederatedIdentityPage.java
@@ -5,12 +5,17 @@ import javax.ws.rs.core.UriBuilder;
 import org.keycloak.services.Urls;
 import org.keycloak.testsuite.Constants;
 import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
 
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
 public class AccountFederatedIdentityPage extends AbstractAccountPage {
 
+    @FindBy(className = "alert-error")
+    private WebElement errorMessage;
+
     public AccountFederatedIdentityPage() {};
 
     private String realmName = "test";
@@ -39,4 +44,8 @@ public class AccountFederatedIdentityPage extends AbstractAccountPage {
     public void clickRemoveProvider(String providerId) {
         driver.findElement(By.id("remove-" + providerId)).click();
     }
+
+    public String getError() {
+        return errorMessage.getText();
+    }
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/IdpConfirmLinkPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/IdpConfirmLinkPage.java
new file mode 100644
index 0000000..83596a0
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/IdpConfirmLinkPage.java
@@ -0,0 +1,41 @@
+package org.keycloak.testsuite.pages;
+
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class IdpConfirmLinkPage extends AbstractPage {
+
+    @FindBy(id = "updateProfile")
+    private WebElement updateProfileButton;
+
+    @FindBy(id = "linkAccount")
+    private WebElement linkAccountButton;
+
+    @FindBy(className = "instruction")
+    private WebElement message;
+
+    @Override
+    public boolean isCurrent() {
+        return driver.getTitle().equals("Account already exists");
+    }
+
+    public String getMessage() {
+        return message.getText();
+    }
+
+    public void clickReviewProfile() {
+        updateProfileButton.click();
+    }
+
+    public void clickLinkAccount() {
+        linkAccountButton.click();
+    }
+
+    @Override
+    public void open() throws Exception {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/IdpLinkEmailPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/IdpLinkEmailPage.java
new file mode 100644
index 0000000..91387b5
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/IdpLinkEmailPage.java
@@ -0,0 +1,27 @@
+package org.keycloak.testsuite.pages;
+
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class IdpLinkEmailPage extends AbstractPage {
+
+    @FindBy(id = "instruction1")
+    private WebElement message;
+
+    @Override
+    public boolean isCurrent() {
+        return driver.getTitle().startsWith("Link ");
+    }
+
+    @Override
+    public void open() throws Exception {
+        throw new UnsupportedOperationException();
+    }
+
+    public String getMessage() {
+        return message.getText();
+    }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java
index f329533..04e5ddd 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java
@@ -112,6 +112,10 @@ public class LoginPage extends AbstractPage {
         return usernameInput.getAttribute("value");
     }
 
+    public boolean isUsernameInputEnabled() {
+        return usernameInput.isEnabled();
+    }
+
     public String getPassword() {
         return passwordInput.getAttribute("value");
     }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java
index d67862c..c9038a4 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java
@@ -21,6 +21,8 @@
  */
 package org.keycloak.testsuite.pages;
 
+import org.junit.Assert;
+import org.openqa.selenium.By;
 import org.openqa.selenium.WebElement;
 import org.openqa.selenium.support.FindBy;
 
diff --git a/testsuite/integration/src/test/resources/adapter-test/demorealm.json b/testsuite/integration/src/test/resources/adapter-test/demorealm.json
index 5bf2bdd..b5cd399 100755
--- a/testsuite/integration/src/test/resources/adapter-test/demorealm.json
+++ b/testsuite/integration/src/test/resources/adapter-test/demorealm.json
@@ -34,12 +34,36 @@
             "lastName": "Posolda",
             "credentials" : [
                 { "type" : "password",
-                  "value" : "password" }
+                    "value" : "password" }
             ],
             "realmRoles": [ "user" ],
             "applicationRoles": {
                 "account": [ "manage-account" ]
             }
+        },
+        {
+            "username" : "topGroupUser",
+            "enabled": true,
+            "email" : "top@redhat.com",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "password" }
+            ],
+            "groups": [
+                "/top"
+            ]
+        },
+        {
+            "username" : "level2GroupUser",
+            "enabled": true,
+            "email" : "level2@redhat.com",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "password" }
+            ],
+            "groups": [
+                "/top/level2"
+            ]
         }
     ],
     "roles" : {
@@ -54,6 +78,29 @@
             }
         ]
     },
+    "groups" : [
+        {
+            "name": "top",
+            "attributes": {
+                "topAttribute": ["true"]
+
+            },
+            "realmRoles": ["user"],
+            "clientRoles": {
+                "account": ["manage-account"]
+            },
+            "subGroups": [
+                {
+                    "name": "level2",
+                    "realmRoles": ["admin"],
+                    "attributes": {
+                        "level2Attribute": ["true"]
+
+                    }
+                }
+            ]
+        }
+    ],
     "scopeMappings": [
         {
             "client": "third-party",
diff --git a/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json b/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json
index 410d373..0b8b79e 100755
--- a/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json
+++ b/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json
@@ -17,7 +17,6 @@
             "alias" : "model-google",
             "providerId" : "google",
             "enabled": true,
-            "updateProfileFirstLogin" : "true",
             "trustEmail" : "true",
             "storeToken": "true",
             "config": {
@@ -29,7 +28,7 @@
             "alias" : "model-facebook",
             "providerId" : "facebook",
             "enabled": true,
-            "updateProfileFirstLogin" : "false",
+            "firstBrokerLoginFlowAlias" : "browser",
             "config": {
                 "authorizationUrl": "authorizationUrl",
                 "tokenUrl": "tokenUrl",
@@ -42,7 +41,6 @@
             "alias" : "model-github",
             "providerId" : "github",
             "enabled": true,
-            "updateProfileFirstLoginMode" : "on",
             "storeToken": "false",
             "config": {
                 "authorizationUrl": "authorizationUrl",
@@ -56,7 +54,6 @@
             "alias" : "model-twitter",
             "providerId" : "twitter",
             "enabled": true,
-            "updateProfileFirstLoginMode" : "off",
             "storeToken": true,
             "config": {
                 "authorizationUrl": "authorizationUrl",
@@ -70,7 +67,6 @@
             "alias" : "model-linkedin",
             "providerId" : "linkedin",
             "enabled": true,
-            "updateProfileFirstLoginMode" : "missing",
             "storeToken": false,
             "config": {
                 "authorizationUrl": "authorizationUrl",
@@ -84,7 +80,6 @@
             "alias" : "model-stackoverflow",
             "providerId" : "stackoverflow",
             "enabled": true,
-            "updateProfileFirstLoginMode" : "off",
             "storeToken": false,
             "config": {
                 "key": "keyValue",
@@ -99,7 +94,6 @@
           "alias" : "model-saml-signed-idp",
           "providerId" : "saml",
           "enabled": true,
-          "updateProfileFirstLoginMode" : "on",
           "config": {
             "singleSignOnServiceUrl": "http://localhost:8082/auth/realms/realm-with-saml-identity-provider/protocol/saml",
             "nameIDPolicyFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
@@ -115,7 +109,6 @@
             "alias" : "kc-saml-signed-idp",
             "providerId" : "saml",
             "enabled": true,
-            "updateProfileFirstLoginMode" : "on",
             "addReadTokenRoleOnCreate": true,
             "config": {
                 "singleSignOnServiceUrl": "http://localhost:8082/auth/realms/realm-with-saml-signed-idp/protocol/saml",
@@ -134,7 +127,6 @@
             "alias" : "kc-saml-idp-basic",
             "providerId" : "saml",
             "enabled": true,
-            "updateProfileFirstLoginMode" : "on",
             "trustEmail" : false,
             "addReadTokenRoleOnCreate": true,
             "config": {
@@ -150,7 +142,6 @@
             "alias" : "model-oidc-idp",
             "providerId" : "oidc",
             "enabled": false,
-            "updateProfileFirstLoginMode" : "off",
             "authenticateByDefault" : "false",
             "config": {
                 "clientId": "clientId",
@@ -166,7 +157,6 @@
             "alias" : "kc-oidc-idp",
             "providerId" : "keycloak-oidc",
             "enabled": true,
-            "updateProfileFirstLoginMode" : "off",
             "storeToken" : true,
             "addReadTokenRoleOnCreate": true,
             "config": {
diff --git a/testsuite/integration/src/test/resources/kerberos/users-kerberos.ldif b/testsuite/integration/src/test/resources/kerberos/users-kerberos.ldif
index fd9936c..9d55092 100644
--- a/testsuite/integration/src/test/resources/kerberos/users-kerberos.ldif
+++ b/testsuite/integration/src/test/resources/kerberos/users-kerberos.ldif
@@ -76,6 +76,20 @@ userPassword: theduke
 krb5PrincipalName: jduke@KEYCLOAK.ORG
 krb5KeyVersionNumber: 0
 
+dn: uid=MyDuke,ou=People,dc=keycloak,dc=org
+objectClass: top
+objectClass: person
+objectClass: inetOrgPerson
+objectClass: krb5principal
+objectClass: krb5kdcentry
+cn: My
+sn: Duke
+mail: MyDuke@keycloak.org
+uid: MyDuke
+userPassword: theduke
+krb5PrincipalName: MyDuke@KEYCLOAK.ORG
+krb5KeyVersionNumber: 0
+
 dn: uid=gsstestserver,ou=People,dc=keycloak,dc=org
 objectClass: top
 objectClass: person
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/testsaml.json b/testsuite/integration/src/test/resources/keycloak-saml/testsaml.json
index 8d5576e..95b9fb9 100755
--- a/testsuite/integration/src/test/resources/keycloak-saml/testsaml.json
+++ b/testsuite/integration/src/test/resources/keycloak-saml/testsaml.json
@@ -40,6 +40,30 @@
                 { "type" : "password",
                     "value" : "password" }
             ]
+        },
+        {
+            "username" : "topGroupUser",
+            "enabled": true,
+            "email" : "top@redhat.com",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "password" }
+            ],
+            "groups": [
+                "/top"
+            ]
+        },
+        {
+            "username" : "level2GroupUser",
+            "enabled": true,
+            "email" : "level2@redhat.com",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "password" }
+            ],
+            "groups": [
+                "/top/level2"
+            ]
         }
     ],
     "applications": [
@@ -347,6 +371,27 @@
             }
         }
     ],
+    "groups" : [
+        {
+            "name": "top",
+            "attributes": {
+                "topAttribute": ["true"]
+
+            },
+            "realmRoles": ["manager"],
+            "subGroups": [
+                {
+                    "name": "level2",
+                    "realmRoles": ["user"],
+                    "attributes": {
+                        "level2Attribute": ["true"]
+
+                    }
+                }
+            ]
+        }
+    ],
+
     "roles" : {
         "realm" : [
             {
diff --git a/testsuite/integration/src/test/resources/testrealm.json b/testsuite/integration/src/test/resources/testrealm.json
index e16e3e2..b4718dd 100755
--- a/testsuite/integration/src/test/resources/testrealm.json
+++ b/testsuite/integration/src/test/resources/testrealm.json
@@ -61,6 +61,30 @@
                 "test-app": [ "customer-user" ],
                 "account": [ "view-profile", "manage-account" ]
             }
+        },
+        {
+            "username" : "topGroupUser",
+            "enabled": true,
+            "email" : "top@redhat.com",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "password" }
+            ],
+            "groups": [
+                "/topGroup"
+            ]
+        },
+        {
+            "username" : "level2GroupUser",
+            "enabled": true,
+            "email" : "level2@redhat.com",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "password" }
+            ],
+            "groups": [
+                "/topGroup/level2group"
+            ]
         }
     ],
     "scopeMappings": [
@@ -120,6 +144,31 @@
         }
 
     },
+    "groups" : [
+        {
+            "name": "topGroup",
+            "attributes": {
+                "topAttribute": ["true"]
+
+            },
+            "realmRoles": ["user"],
+
+            "subGroups": [
+                {
+                    "name": "level2group",
+                    "realmRoles": ["admin"],
+                    "clientRoles": {
+                        "test-app": ["customer-user"]
+                    },
+                    "attributes": {
+                        "level2Attribute": ["true"]
+
+                    }
+                }
+            ]
+        }
+    ],
+
 
     "clientScopeMappings": {
         "test-app": [
diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc12/pom.xml b/testsuite/integration-arquillian/servers/migration/wildfly_kc12/pom.xml
new file mode 100644
index 0000000..132b1af
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/migration/wildfly_kc12/pom.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+<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-migration-servers</artifactId>
+        <version>1.7.0.Final-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>integration-arquillian-server-wildfly-kc12</artifactId>
+    <packaging>pom</packaging>
+    <name>Keycloak 1.2.0.Final on Wildfly</name>
+    
+    <properties>
+        <server.version>1.2.0.Final</server.version>
+        <keycloak.server.home>${project.build.directory}/unpacked/keycloak-${server.version}</keycloak.server.home>
+        <jdbc.mvn.driver.deployment.dir>${keycloak.server.home}/modules/system/layers/base/com/${jdbc.mvn.artifactId}/main</jdbc.mvn.driver.deployment.dir>
+    </properties>
+    
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-enforcer-plugin</artifactId>
+                <configuration>
+                    <skip>false</skip>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>xml-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-assembly-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc13/pom.xml b/testsuite/integration-arquillian/servers/migration/wildfly_kc13/pom.xml
new file mode 100644
index 0000000..5d7bb1c
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/migration/wildfly_kc13/pom.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0"?>
+<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-migration-servers</artifactId>
+        <version>1.7.0.Final-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>integration-arquillian-server-wildfly-kc13</artifactId>
+    <packaging>pom</packaging>
+    <name>Keycloak 1.3.1.Final on Wildfly</name>
+    
+    <properties>
+        <server.version>1.3.1.Final</server.version>
+        <keycloak.server.home>${project.build.directory}/unpacked/keycloak-${server.version}</keycloak.server.home>
+        <jdbc.mvn.driver.deployment.dir>${keycloak.server.home}/modules/system/layers/base/com/${jdbc.mvn.artifactId}/main</jdbc.mvn.driver.deployment.dir>
+    </properties>
+    
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-enforcer-plugin</artifactId>
+                <configuration>
+                    <skip>false</skip>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>xml-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-assembly-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+    
+</project>
diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc14/pom.xml b/testsuite/integration-arquillian/servers/migration/wildfly_kc14/pom.xml
new file mode 100644
index 0000000..85dcff3
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/migration/wildfly_kc14/pom.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+<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-migration-servers</artifactId>
+        <version>1.7.0.Final-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>integration-arquillian-server-wildfly-kc14</artifactId>
+    <packaging>pom</packaging>
+    <name>Keycloak 1.4.0.Final on Wildfly</name>
+    
+    <properties>
+        <server.version>1.4.0.Final</server.version>
+        <keycloak.server.home>${project.build.directory}/unpacked/keycloak-${server.version}</keycloak.server.home>
+        <jdbc.mvn.driver.deployment.dir>${keycloak.server.home}/modules/system/layers/base/com/${jdbc.mvn.artifactId}/main</jdbc.mvn.driver.deployment.dir>
+    </properties>
+    
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-enforcer-plugin</artifactId>
+                <configuration>
+                    <skip>false</skip>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>xml-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-assembly-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc15/pom.xml b/testsuite/integration-arquillian/servers/migration/wildfly_kc15/pom.xml
new file mode 100644
index 0000000..802f50a
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/migration/wildfly_kc15/pom.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+<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-migration-servers</artifactId>
+        <version>1.7.0.Final-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>integration-arquillian-server-wildfly-kc15</artifactId>
+    <packaging>pom</packaging>
+    <name>Keycloak 1.5.1.Final on Wildfly</name>
+    
+        <properties>
+        <server.version>1.5.1.Final</server.version>
+        <keycloak.server.home>${project.build.directory}/unpacked/keycloak-${server.version}</keycloak.server.home>
+        <jdbc.mvn.driver.deployment.dir>${keycloak.server.home}/modules/system/layers/base/com/${jdbc.mvn.artifactId}/main</jdbc.mvn.driver.deployment.dir>
+    </properties>
+    
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-enforcer-plugin</artifactId>
+                <configuration>
+                    <skip>false</skip>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>xml-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-assembly-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc16/pom.xml b/testsuite/integration-arquillian/servers/migration/wildfly_kc16/pom.xml
new file mode 100644
index 0000000..5e27a3a
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/migration/wildfly_kc16/pom.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+<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-migration-servers</artifactId>
+        <version>1.7.0.Final-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>integration-arquillian-server-wildfly-kc16</artifactId>
+    <packaging>pom</packaging>
+    <name>Keycloak 1.6.1.Final on Wildfly</name>
+    
+    <properties>
+        <server.version>1.6.1.Final</server.version>
+        <keycloak.server.home>${project.build.directory}/unpacked/keycloak-${server.version}</keycloak.server.home>
+        <jdbc.mvn.driver.deployment.dir>${keycloak.server.home}/modules/system/layers/base/com/${jdbc.mvn.artifactId}/main</jdbc.mvn.driver.deployment.dir>
+    </properties>
+    
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-enforcer-plugin</artifactId>
+                <configuration>
+                    <skip>false</skip>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>xml-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-assembly-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/testsuite/integration-arquillian/servers/pom.xml b/testsuite/integration-arquillian/servers/pom.xml
index d97c97c..7175811 100644
--- a/testsuite/integration-arquillian/servers/pom.xml
+++ b/testsuite/integration-arquillian/servers/pom.xml
@@ -12,6 +12,10 @@
     <packaging>pom</packaging>
     <name>Servers</name>
 
+    <modules>
+        <module>migration</module>
+    </modules>
+    
     <profiles>
         <profile>
             <id>auth-server-wildfly</id>
@@ -26,36 +30,6 @@
                 <module>eap6</module>
             </modules>
         </profile>
-        <profile>
-            <id>migration-kc16</id>
-            <modules>
-                <module>wildfly_kc16</module>
-            </modules>
-        </profile>
-        <profile>
-            <id>migration-kc15</id>
-            <modules>
-                <module>wildfly_kc15</module>
-            </modules>
-        </profile>
-        <profile>
-            <id>migration-kc14</id>
-            <modules>
-                <module>wildfly_kc14</module>
-            </modules>
-        </profile>
-        <profile>
-            <id>migration-kc13</id>
-            <modules>
-                <module>wildfly_kc13</module>
-            </modules>
-        </profile>
-        <profile>
-            <id>migration-kc12</id>
-            <modules>
-                <module>wildfly_kc12</module>
-            </modules>
-        </profile>
     </profiles>    
 
 </project>
diff --git a/testsuite/integration-arquillian/tests/adapters/as7/pom.xml b/testsuite/integration-arquillian/tests/adapters/as7/pom.xml
index 313c154..15b575c 100644
--- a/testsuite/integration-arquillian/tests/adapters/as7/pom.xml
+++ b/testsuite/integration-arquillian/tests/adapters/as7/pom.xml
@@ -25,14 +25,13 @@
             <type>zip</type>
         </dependency>
         <dependency>
-            <groupId>org.keycloak</groupId>
-            <artifactId>keycloak-as7-adapter-dist</artifactId>
-            <type>zip</type>
+            <groupId>org.wildfly</groupId>
+            <artifactId>wildfly-arquillian-container-managed</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.jboss.as</groupId>
-            <artifactId>jboss-as-arquillian-container-managed</artifactId>
-            <version>7.2.0.Final</version>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-eap6-adapter-dist</artifactId>
+            <type>zip</type>
         </dependency>
     </dependencies>
 
@@ -67,18 +66,20 @@
                                 <artifactItem>
                                     <groupId>org.keycloak</groupId>
                                     <artifactId>keycloak-as7-adapter-dist</artifactId>
+                                    <version>${project.version}</version>
                                     <type>zip</type>
                                     <outputDirectory>${adapter.libs.as7}</outputDirectory>
                                 </artifactItem>
                             </artifactItems>
-                            <overWriteIfNewer>true</overWriteIfNewer>
                         </configuration>
                     </execution>
                 </executions>
             </plugin>
+            
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-surefire-plugin</artifactId>
+                <version>2.18.1</version>
                 <configuration>
                     <systemPropertyVariables>
                         <app.server.as7>true</app.server.as7>
@@ -90,7 +91,6 @@
         </plugins>
     </build>
     
-    
     <profiles>
         <profile>
             <id>adapter-libs-provided</id>
@@ -133,5 +133,4 @@
             </build>
         </profile>
     </profiles>        
-    
 </project>
diff --git a/testsuite/integration-arquillian/tests/adapters/as7/src/main/xslt/arquillian.xsl b/testsuite/integration-arquillian/tests/adapters/as7/src/main/xslt/arquillian.xsl
index 8970850..9ba1e94 100644
--- a/testsuite/integration-arquillian/tests/adapters/as7/src/main/xslt/arquillian.xsl
+++ b/testsuite/integration-arquillian/tests/adapters/as7/src/main/xslt/arquillian.xsl
@@ -18,6 +18,7 @@
                     <property name="jbossHome">${app.server.as7.home}</property>
                     <property name="javaVmArguments">-Djboss.socket.binding.port-offset=${app.server.port.offset} -Xms64m -Xmx512m -XX:MaxPermSize=256m ${adapter.test.props}</property>
                     <property name="managementAddress">localhost</property>
+                    <property name="managementProtocol">remote</property>
                     <property name="managementPort">${app.server.management.port.jmx}</property>
                 </configuration>
             </container>
diff --git a/testsuite/integration-arquillian/tests/adapters/as7/src/test/java/org/keycloak/testsuite/adapter/servlet/AS7DemoServletsAdapterTest.java b/testsuite/integration-arquillian/tests/adapters/as7/src/test/java/org/keycloak/testsuite/adapter/servlet/AS7DemoServletsAdapterTest.java
index f0258b2..1a4a68c 100644
--- a/testsuite/integration-arquillian/tests/adapters/as7/src/test/java/org/keycloak/testsuite/adapter/servlet/AS7DemoServletsAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/adapters/as7/src/test/java/org/keycloak/testsuite/adapter/servlet/AS7DemoServletsAdapterTest.java
@@ -1,5 +1,6 @@
 package org.keycloak.testsuite.adapter.servlet;
 
+import org.keycloak.testsuite.arquillian.annotation.AdapterLibsLocationProperty;
 import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
 
 /**
@@ -7,6 +8,7 @@ import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
  * @author tkyjovsk
  */
 @AppServerContainer("app-server-as7")
+@AdapterLibsLocationProperty("adapter.libs.as7")
 public class AS7DemoServletsAdapterTest extends AbstractDemoServletsAdapterTest {
 
 }
diff --git a/testsuite/integration-arquillian/tests/adapters/as7/src/test/java/org/keycloak/testsuite/adapter/servlet/AS7SessionServletAdapterTest.java b/testsuite/integration-arquillian/tests/adapters/as7/src/test/java/org/keycloak/testsuite/adapter/servlet/AS7SessionServletAdapterTest.java
index 9362ecd..4b88033 100644
--- a/testsuite/integration-arquillian/tests/adapters/as7/src/test/java/org/keycloak/testsuite/adapter/servlet/AS7SessionServletAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/adapters/as7/src/test/java/org/keycloak/testsuite/adapter/servlet/AS7SessionServletAdapterTest.java
@@ -1,5 +1,6 @@
 package org.keycloak.testsuite.adapter.servlet;
 
+import org.keycloak.testsuite.arquillian.annotation.AdapterLibsLocationProperty;
 import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
 
 /**
@@ -7,6 +8,7 @@ import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
  * @author tkyjovsk
  */
 @AppServerContainer("app-server-as7")
+@AdapterLibsLocationProperty("adapter.libs.as7")
 public class AS7SessionServletAdapterTest extends AbstractSessionServletAdapterTest {
 
 }
diff --git a/testsuite/integration-arquillian/tests/adapters/eap6/pom.xml b/testsuite/integration-arquillian/tests/adapters/eap6/pom.xml
new file mode 100644
index 0000000..433ca63
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/adapters/eap6/pom.xml
@@ -0,0 +1,135 @@
+<?xml version="1.0"?>
+<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-tests-adapters</artifactId>
+        <version>1.7.0.Final-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>integration-arquillian-adapters-eap6</artifactId>
+    <name>Adapter Tests on EAP 6</name>
+    
+    <properties>
+        <app.server.eap6.home>${containers.home}/jboss-eap-6.4</app.server.eap6.home>
+        <adapter.libs.eap6>${containers.home}/keycloak-eap6-adapter-dist</adapter.libs.eap6>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.jboss.as</groupId>
+            <artifactId>jboss-as-dist</artifactId>
+            <version>${jboss.version}</version>
+            <type>zip</type>
+        </dependency>
+        <dependency>
+            <groupId>org.wildfly</groupId>
+            <artifactId>wildfly-arquillian-container-managed</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-eap6-adapter-dist</artifactId>
+            <type>zip</type>
+        </dependency>
+    </dependencies>
+
+    <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-as7-and-adapter</id>
+                        <phase>generate-resources</phase>
+                        <goals>
+                            <goal>unpack</goal>
+                        </goals>
+                        <configuration>
+                            <artifactItems>
+                                <artifactItem>
+                                    <groupId>org.jboss.as</groupId>
+                                    <artifactId>jboss-as-dist</artifactId>
+                                    <version>${jboss.version}</version>
+                                    <type>zip</type>
+                                    <outputDirectory>${containers.home}</outputDirectory>
+                                </artifactItem>
+                                <artifactItem>
+                                    <groupId>org.keycloak</groupId>
+                                    <artifactId>keycloak-eap6-adapter-dist</artifactId>
+                                    <version>${project.version}</version>
+                                    <type>zip</type>
+                                    <outputDirectory>${adapter.libs.eap6}</outputDirectory>
+                                </artifactItem>
+                            </artifactItems>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <version>2.18.1</version>
+                <configuration>
+                    <systemPropertyVariables>
+                        <app.server.eap6>true</app.server.eap6>
+                        <app.server.eap6.home>${app.server.eap6.home}</app.server.eap6.home>
+                        <adapter.libs.eap6>${adapter.libs.eap6}</adapter.libs.eap6>
+                    </systemPropertyVariables>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+    
+    <profiles>
+        <profile>
+            <id>adapter-libs-provided</id>
+            <activation>    
+                <property>
+                    <name>!adapter.libs.bundled</name>
+                </property>
+            </activation>
+            <properties>
+                <adapter.libs.eap6>${app.server.eap6.home}</adapter.libs.eap6>
+            </properties>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>xml-maven-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>configure-adapter-subsystem</id>
+                                <phase>process-resources</phase>
+                                <goals>
+                                    <goal>transform</goal>
+                                </goals>
+                                <configuration>
+                                    <transformationSets>
+                                        <transformationSet>
+                                            <dir>${app.server.eap6.home}/standalone/configuration</dir>
+                                            <includes>
+                                                <include>standalone.xml</include>
+                                            </includes>
+                                            <stylesheet>src/main/xslt/standalone.xsl</stylesheet>
+                                            <outputDir>${app.server.eap6.home}/standalone/configuration</outputDir>
+                                        </transformationSet>
+                                    </transformationSets>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>        
+</project>
diff --git a/testsuite/integration-arquillian/tests/adapters/eap6/src/main/xslt/arquillian.xsl b/testsuite/integration-arquillian/tests/adapters/eap6/src/main/xslt/arquillian.xsl
new file mode 100644
index 0000000..fbfd50d
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/adapters/eap6/src/main/xslt/arquillian.xsl
@@ -0,0 +1,37 @@
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                xmlns:xalan="http://xml.apache.org/xalan"
+                xmlns:a="http://jboss.org/schema/arquillian"
+                version="2.0"
+                exclude-result-prefixes="xalan a">
+
+    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" xalan:indent-amount="4" standalone="no"/>
+    <xsl:strip-space elements="*"/>
+
+    <xsl:template match="/a:arquillian">
+        <xsl:copy>
+            <xsl:apply-templates select="node()|@*"/>
+            
+            <container qualifier="app-server-eap6" mode="manual" >
+                <configuration>
+                    <property name="enabled">${app.server.eap6}</property>
+                    <property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
+                    <property name="jbossHome">${app.server.eap6.home}</property>
+                    <property name="javaVmArguments">-Djboss.socket.binding.port-offset=${app.server.port.offset} -Xms64m -Xmx512m -XX:MaxPermSize=256m ${adapter.test.props}</property>
+                    <property name="managementAddress">localhost</property>
+                    <property name="managementProtocol">remote</property>
+                    <property name="managementPort">${app.server.management.port.jmx}</property>
+                </configuration>
+            </container>
+
+        </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/adapters/eap6/src/main/xslt/standalone.xsl b/testsuite/integration-arquillian/tests/adapters/eap6/src/main/xslt/standalone.xsl
new file mode 100644
index 0000000..fb3612b
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/adapters/eap6/src/main/xslt/standalone.xsl
@@ -0,0 +1,51 @@
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                xmlns:xalan="http://xml.apache.org/xalan"
+                xmlns:j="urn:jboss:domain:1.7"
+                xmlns:ds="urn:jboss:domain:datasources:1.2"
+                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: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="*"/>
+
+    <xsl:template match="//j:extensions">
+        <xsl:copy>
+            <xsl:apply-templates select="node()|@*"/>
+            <extension module="org.keycloak.keycloak-adapter-subsystem"/>
+        </xsl:copy>
+    </xsl:template>
+
+    <xsl:template match="//j:profile">
+        <xsl:copy>
+            <xsl:apply-templates select="node()|@*"/>
+            <subsystem xmlns="urn:jboss:domain:keycloak:1.1"/>
+        </xsl:copy>
+    </xsl:template>
+
+    <xsl:template match="//sec:security-domains">
+        <xsl:copy>
+            <xsl:apply-templates select="node()[name(.)='security-domain']"/>
+            <security-domain name="keycloak">
+                <authentication>
+                    <login-module code="org.keycloak.adapters.jboss.KeycloakLoginModule" flag="required"/>
+                </authentication>
+            </security-domain>
+            <security-domain name="sp" cache-type="default">
+                <authentication>
+                    <login-module code="org.picketlink.identity.federation.bindings.wildfly.SAML2LoginModule" flag="required"/>
+                </authentication>
+            </security-domain>
+        </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/adapters/eap6/src/test/java/org/keycloak/testsuite/adapter/servlet/EAP6DemoServletsAdapterTest.java b/testsuite/integration-arquillian/tests/adapters/eap6/src/test/java/org/keycloak/testsuite/adapter/servlet/EAP6DemoServletsAdapterTest.java
new file mode 100644
index 0000000..5eb363e
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/adapters/eap6/src/test/java/org/keycloak/testsuite/adapter/servlet/EAP6DemoServletsAdapterTest.java
@@ -0,0 +1,14 @@
+package org.keycloak.testsuite.adapter.servlet;
+
+import org.keycloak.testsuite.arquillian.annotation.AdapterLibsLocationProperty;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+@AppServerContainer("app-server-eap6")
+@AdapterLibsLocationProperty("adapter.libs.eap6")
+public class EAP6DemoServletsAdapterTest extends AbstractDemoServletsAdapterTest {
+
+}
diff --git a/testsuite/integration-arquillian/tests/adapters/eap6/src/test/java/org/keycloak/testsuite/adapter/servlet/EAP6SessionServletAdapterTest.java b/testsuite/integration-arquillian/tests/adapters/eap6/src/test/java/org/keycloak/testsuite/adapter/servlet/EAP6SessionServletAdapterTest.java
new file mode 100644
index 0000000..c187910
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/adapters/eap6/src/test/java/org/keycloak/testsuite/adapter/servlet/EAP6SessionServletAdapterTest.java
@@ -0,0 +1,14 @@
+package org.keycloak.testsuite.adapter.servlet;
+
+import org.keycloak.testsuite.arquillian.annotation.AdapterLibsLocationProperty;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+@AppServerContainer("app-server-eap6")
+@AdapterLibsLocationProperty("adapter.libs.eap6")
+public class EAP6SessionServletAdapterTest extends AbstractSessionServletAdapterTest {
+
+}
diff --git a/testsuite/integration-arquillian/tests/adapters/karaf/pom.xml b/testsuite/integration-arquillian/tests/adapters/karaf/pom.xml
index fc29132..e6c72f3 100644
--- a/testsuite/integration-arquillian/tests/adapters/karaf/pom.xml
+++ b/testsuite/integration-arquillian/tests/adapters/karaf/pom.xml
@@ -12,8 +12,6 @@
     <name>Adapter Tests on Karaf</name>
     
     <properties>
-        <karaf.version>3.0.3</karaf.version>
-        <karaf.home>${project.build.directory}/assembly</karaf.home>
                 
         <!--fuse examples expect auth server on 8080-->
         <auth.server.port.offset>0</auth.server.port.offset>
@@ -25,83 +23,111 @@
     </properties>
 
     <dependencies>
-        <!-- for karaf-maven-plugin -->
-        <dependency>
-            <groupId>org.apache.karaf.features</groupId>
-            <artifactId>framework</artifactId>
-            <version>${karaf.version}</version>
-            <type>kar</type>
-        </dependency>
+
         <dependency>
-            <groupId>org.apache.camel.karaf</groupId>
-            <artifactId>apache-camel</artifactId>
-            <version>2.12.5</version>
-            <classifier>features</classifier>
-            <type>xml</type>
-            <scope>runtime</scope>
+            <groupId>org.jboss.arquillian.container</groupId>
+            <artifactId>arquillian-container-karaf-managed</artifactId>
+            <version>2.1.0.CR18</version>
         </dependency>
+        
         <dependency>
-            <groupId>org.apache.cxf.karaf</groupId>
-            <artifactId>apache-cxf</artifactId>
-            <version>2.7.14</version>
-            <classifier>features</classifier>
-            <type>xml</type>
-            <scope>runtime</scope>
+            <groupId>org.apache.aries.jmx</groupId>
+            <artifactId>org.apache.aries.jmx</artifactId>
+            <version>1.1.1</version>
         </dependency>
+        
         <dependency>
-            <groupId>org.keycloak</groupId>
-            <artifactId>keycloak-osgi-features</artifactId>
-            <version>${project.version}</version>
-            <classifier>features</classifier>
-            <type>xml</type>
-            <scope>runtime</scope>
+            <groupId>jline</groupId>
+            <artifactId>jline</artifactId>
+            <version>2.12</version>
         </dependency>
         <dependency>
-            <groupId>org.keycloak.example.demo</groupId>
-            <artifactId>keycloak-fuse-example-features</artifactId>
-            <version>${project.version}</version>
-            <classifier>features</classifier>
-            <type>xml</type>
-            <scope>runtime</scope>
+            <groupId>org.apache.sshd</groupId>
+            <artifactId>sshd-core</artifactId>
+            <version>0.12.0</version>
         </dependency>
-        <!-- for arquillian -->
         <dependency>
-            <groupId>org.jboss.arquillian.container</groupId>
-            <artifactId>arquillian-container-karaf-managed</artifactId>
-            <version>2.1.0.CR18</version>
+            <groupId>org.apache.karaf.shell</groupId>
+            <artifactId>org.apache.karaf.shell.console</artifactId>
+            <version>3.0.3</version>
         </dependency>
         <dependency>
-            <groupId>org.apache.aries.jmx</groupId>
-            <artifactId>org.apache.aries.jmx</artifactId>
-            <version>1.1.1</version>
-        </dependency>
+            <groupId>org.apache.karaf</groupId>
+            <artifactId>org.apache.karaf.client</artifactId>
+            <version>3.0.3</version>
+        </dependency>  
+        
     </dependencies>
-    
+
+    <profiles>
+        
+        <profile>
+            <id>fuse</id>
+            <activation>
+                <property>
+                    <name>fuse.home</name>
+                </property>
+            </activation>
+            <properties>
+                <karaf.home>${fuse.home}</karaf.home>
+                <arquillian.xml.stylesheet>src/main/xslt/arquillian-fuse.xsl</arquillian.xml.stylesheet>
+            </properties>
+        </profile>
+        
+        <profile>
+            <id>karaf</id>
+            <activation>
+                <property>
+                    <name>!fuse.home</name>
+                </property>
+            </activation>
+            <properties>
+                <karaf.version>3.0.3</karaf.version>
+                <karaf.home>${containers.home}/apache-karaf-minimal-${karaf.version}</karaf.home>
+            </properties>
+            <dependencies>
+                <dependency>
+                    <groupId>org.apache.karaf</groupId>
+                    <artifactId>apache-karaf-minimal</artifactId>
+                    <version>${karaf.version}</version>
+                    <type>zip</type>
+                </dependency>
+            </dependencies>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-dependency-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>unpack-karaf</id>
+                                <phase>generate-resources</phase>
+                                <goals>
+                                    <goal>unpack</goal>
+                                </goals>
+                                <configuration>
+                                    <artifactItems>
+                                        <artifactItem>
+                                            <groupId>org.apache.karaf</groupId>
+                                            <artifactId>apache-karaf-minimal</artifactId>
+                                            <version>${karaf.version}</version>
+                                            <type>zip</type>
+                                            <outputDirectory>${containers.home}</outputDirectory>
+                                        </artifactItem>
+                                    </artifactItems>
+                                    <overWriteIfNewer>true</overWriteIfNewer>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>        
+        </profile>
+    </profiles>
+
     <build>
         <plugins>
             <plugin>
-                <groupId>org.apache.karaf.tooling</groupId>
-                <artifactId>karaf-maven-plugin</artifactId>
-                <version>${karaf.version}</version>
-                <extensions>true</extensions>
-                <executions>
-                    <execution>
-                        <id>prepare-karaf-with-examples</id>
-                        <phase>generate-test-resources</phase>
-                        <goals>
-                            <!-- creates custom karaf distro in ${project.build.directory}/assembly -->
-                            <goal>install-kars</goal>
-                        </goals>
-                    </execution>
-                </executions>
-                <configuration>
-                    <bootFeatures>
-                        <!-- this installs all fuse examples -->
-                        <feature>keycloak-fuse-example</feature>
-                    </bootFeatures>
-                </configuration>
-            </plugin>
-            <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-surefire-plugin</artifactId>
                 <configuration>
@@ -117,5 +143,5 @@
             </plugin>
         </plugins>
     </build>
-
+            
 </project>
diff --git a/testsuite/integration-arquillian/tests/adapters/karaf/src/main/java/org/keycloak/testsuite/arquillian/karaf/CustomKarafContainer.java b/testsuite/integration-arquillian/tests/adapters/karaf/src/main/java/org/keycloak/testsuite/arquillian/karaf/CustomKarafContainer.java
new file mode 100644
index 0000000..e9f1212
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/adapters/karaf/src/main/java/org/keycloak/testsuite/arquillian/karaf/CustomKarafContainer.java
@@ -0,0 +1,114 @@
+package org.keycloak.testsuite.arquillian.karaf;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import javax.management.InstanceNotFoundException;
+import javax.management.MBeanServerConnection;
+import javax.management.ObjectName;
+import org.jboss.arquillian.container.osgi.jmx.ObjectNameFactory;
+import org.jboss.arquillian.container.osgi.karaf.managed.KarafManagedDeployableContainer;
+import org.jboss.arquillian.container.spi.client.container.LifecycleException;
+import org.jboss.logging.Logger;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+public class CustomKarafContainer extends KarafManagedDeployableContainer<CustomKarafContainerConfiguration> {
+
+    protected final Logger log = Logger.getLogger(this.getClass());
+
+    private CustomKarafContainerConfiguration config;
+
+    protected MBeanServerConnection mbeanServer = null;
+    protected ObjectName feature;
+
+    @Override
+    public void start() throws LifecycleException {
+        super.start();
+        executeAfterStartCommands();
+    }
+
+    @Override
+    public void setup(CustomKarafContainerConfiguration config) {
+        super.setup(config);
+        this.config = config;
+    }
+
+    @Override
+    public Class<CustomKarafContainerConfiguration> getConfigurationClass() {
+        return CustomKarafContainerConfiguration.class;
+    }
+
+    protected void executeAfterStartCommands() throws LifecycleException {
+        try {
+            mbeanServer = getMBeanServerConnection(500, TimeUnit.MILLISECONDS);
+        } catch (TimeoutException ex) {
+            throw new LifecycleException("JMX connection timed out.");
+        }
+
+        try {
+
+            feature = ObjectNameFactory.create("org.apache.karaf:type=feature,name=root");
+            try {
+                mbeanServer.getObjectInstance(feature);
+            } catch (InstanceNotFoundException infe) {
+                try {
+                    feature = ObjectNameFactory.create("org.apache.karaf:type=features,name=root");
+                    mbeanServer.getObjectInstance(feature);
+                } catch (InstanceNotFoundException infe2) {
+                    throw new RuntimeException("Feature MBean not found on server.");
+                }
+            }
+
+            featureMBean = getMBeanProxy(mbeanServer, feature, FeatureMBean.class, 30, TimeUnit.SECONDS);
+
+            log.info("Executing karaf after-start commands");
+            for (String command : config.getCommandsAfterStartAsArray()) {
+                String cmd = command.trim().split(" ")[0].trim();
+                String param = command.trim().split(" ")[1].trim();
+                log.info(String.format("command: %s, param: %s", cmd, param));
+                switch (cmd) {
+                    case "feature:repo-add":
+                    case "features:addurl":
+                        featureMBean.addRepository(param);
+                        break;
+                    case "feature:repo-remove":
+                    case "features:removeurl":
+                        featureMBean.removeRepository(param);
+                        break;
+                    case "feature:install":
+                    case "features:install":
+                        featureMBean.installFeature(param);
+                        break;
+                    case "feature:uninstall":
+                    case "features:uninstall":
+                        featureMBean.uninstallFeature(param);
+                        break;
+                    default:
+                        throw new RuntimeException(String.format("Unsupported command: '%s'. "
+                                + "Supported after-start commands for Karaf: 'feature:repo-add', 'feature:install', 'feature:repo-remove', 'feature:uninstall'\n"
+                                + "Supported after-start commands for Fuse: 'features:addurl', 'features:install', 'features:removeurl', 'features:uninstall'", cmd));
+                }
+            }
+        } catch (IOException | RuntimeException | TimeoutException ex) {
+            stop();
+            throw new LifecycleException("Error when executing karaf after-start commands.", ex);
+        }
+    }
+
+    FeatureMBean featureMBean;
+
+    public interface FeatureMBean {
+
+        public void addRepository(String repository);
+
+        public void removeRepository(String repository);
+
+        public void installFeature(String feature);
+
+        public void uninstallFeature(String feature);
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/adapters/karaf/src/main/java/org/keycloak/testsuite/arquillian/karaf/CustomKarafContainerConfiguration.java b/testsuite/integration-arquillian/tests/adapters/karaf/src/main/java/org/keycloak/testsuite/arquillian/karaf/CustomKarafContainerConfiguration.java
new file mode 100644
index 0000000..05d063c
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/adapters/karaf/src/main/java/org/keycloak/testsuite/arquillian/karaf/CustomKarafContainerConfiguration.java
@@ -0,0 +1,25 @@
+package org.keycloak.testsuite.arquillian.karaf;
+
+import org.jboss.arquillian.container.osgi.karaf.managed.KarafManagedContainerConfiguration;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+public class CustomKarafContainerConfiguration extends KarafManagedContainerConfiguration {
+
+    private String commandsAfterStart;
+
+    public String getCommandsAfterStart() {
+        return commandsAfterStart;
+    }
+
+    public String[] getCommandsAfterStartAsArray() {
+        return getCommandsAfterStart().trim().split(",");
+    }
+
+    public void setCommandsAfterStart(String commandsAfterStart) {
+        this.commandsAfterStart = commandsAfterStart;
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/adapters/karaf/src/main/java/org/keycloak/testsuite/arquillian/karaf/CustomKarafContainerExtension.java b/testsuite/integration-arquillian/tests/adapters/karaf/src/main/java/org/keycloak/testsuite/arquillian/karaf/CustomKarafContainerExtension.java
new file mode 100644
index 0000000..3a5611f
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/adapters/karaf/src/main/java/org/keycloak/testsuite/arquillian/karaf/CustomKarafContainerExtension.java
@@ -0,0 +1,17 @@
+package org.keycloak.testsuite.arquillian.karaf;
+
+import org.jboss.arquillian.container.spi.client.container.DeployableContainer;
+import org.jboss.arquillian.core.spi.LoadableExtension;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+public class CustomKarafContainerExtension implements LoadableExtension {
+
+    @Override
+    public void register(ExtensionBuilder builder) {
+        builder.service(DeployableContainer.class, CustomKarafContainer.class);
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/adapters/karaf/src/main/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension b/testsuite/integration-arquillian/tests/adapters/karaf/src/main/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension
new file mode 100644
index 0000000..2c03587
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/adapters/karaf/src/main/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension
@@ -0,0 +1 @@
+org.keycloak.testsuite.arquillian.karaf.CustomKarafContainerExtension
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/adapters/karaf/src/main/xslt/arquillian.xsl b/testsuite/integration-arquillian/tests/adapters/karaf/src/main/xslt/arquillian.xsl
index fabd47b..3d6f718 100644
--- a/testsuite/integration-arquillian/tests/adapters/karaf/src/main/xslt/arquillian.xsl
+++ b/testsuite/integration-arquillian/tests/adapters/karaf/src/main/xslt/arquillian.xsl
@@ -4,6 +4,8 @@
                 version="2.0"
                 exclude-result-prefixes="xalan a">
 
+    <xsl:param name="keycloak.version" />
+    
     <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" xalan:indent-amount="4" standalone="no"/>
     <xsl:strip-space elements="*"/>
 
@@ -14,13 +16,24 @@
             <container qualifier="app-server-karaf" mode="manual" >
                 <configuration>
                     <property name="enabled">${app.server.karaf}</property>
-                    <property name="adapterImplClass">org.jboss.arquillian.container.osgi.karaf.managed.KarafManagedDeployableContainer</property>
+                    <!--<property name="adapterImplClass">org.jboss.arquillian.container.osgi.karaf.managed.KarafManagedDeployableContainer</property>-->
+                    <property name="adapterImplClass">org.keycloak.testsuite.arquillian.karaf.CustomKarafContainer</property>
                     <property name="autostartBundle">false</property>
                     <property name="karafHome">${karaf.home}</property>
                     <property name="javaVmArguments">-agentlib:jdwp=transport=dt_socket,address=5005,server=y,suspend=n ${adapter.test.props}</property>
                     <property name="jmxServiceURL">service:jmx:rmi://127.0.0.1:44444/jndi/rmi://127.0.0.1:1099/karaf-root</property>
                     <property name="jmxUsername">karaf</property>
-                    <property name="jmxPassword">karaf</property>            
+                    <property name="jmxPassword">karaf</property>          
+                   
+                    <!-- The following commands are performed by the CustomKarafContainer -->
+                    <property name="commandsAfterStart">
+                        feature:repo-add mvn:org.apache.camel.karaf/apache-camel/2.15.1/xml/features,
+                        feature:repo-add mvn:org.apache.cxf.karaf/apache-cxf/3.0.4/xml/features,
+                        feature:repo-add mvn:org.keycloak/keycloak-osgi-features/<xsl:value-of select="$keycloak.version"/>/xml/features,
+                        feature:repo-add mvn:org.keycloak.example.demo/keycloak-fuse-example-features/<xsl:value-of select="$keycloak.version"/>/xml/features,
+                        feature:install keycloak-fuse-example
+                    </property>
+                          
                 </configuration>
             </container>
     
diff --git a/testsuite/integration-arquillian/tests/adapters/karaf/src/main/xslt/arquillian-fuse.xsl b/testsuite/integration-arquillian/tests/adapters/karaf/src/main/xslt/arquillian-fuse.xsl
new file mode 100644
index 0000000..6c6345e
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/adapters/karaf/src/main/xslt/arquillian-fuse.xsl
@@ -0,0 +1,47 @@
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                xmlns:xalan="http://xml.apache.org/xalan"
+                xmlns:a="http://jboss.org/schema/arquillian"
+                version="2.0"
+                exclude-result-prefixes="xalan a">
+
+    <xsl:param name="keycloak.version" />
+    
+    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" xalan:indent-amount="4" standalone="no"/>
+    <xsl:strip-space elements="*"/>
+
+    <xsl:template match="/a:arquillian">
+        <xsl:copy>
+            <xsl:apply-templates select="node()|@*"/>
+            
+            <container qualifier="app-server-karaf" mode="manual" >
+                <configuration>
+                    <property name="enabled">${app.server.karaf}</property>
+                    <property name="adapterImplClass">org.keycloak.testsuite.arquillian.karaf.CustomKarafContainer</property>
+                    <property name="autostartBundle">false</property>
+                    <property name="karafHome">${karaf.home}</property>
+                    <property name="javaVmArguments">-agentlib:jdwp=transport=dt_socket,address=5005,server=y,suspend=n ${adapter.test.props}</property>
+                    <property name="jmxServiceURL">service:jmx:rmi://127.0.0.1:44444/jndi/rmi://127.0.0.1:1099/karaf-root</property>
+                    <property name="jmxUsername">admin</property>
+                    <property name="jmxPassword">admin</property>          
+                    
+                    <property name="commandsAfterStart">
+                        features:addurl mvn:org.keycloak/keycloak-osgi-features/<xsl:value-of select="$keycloak.version"/>/xml/features,
+                        features:addurl mvn:org.keycloak.example.demo/keycloak-fuse-example-features/<xsl:value-of select="$keycloak.version"/>/xml/features,
+                        features:install keycloak-fuse-example
+                    </property>
+                    
+                </configuration>
+            </container>
+    
+        </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/adapters/pom.xml b/testsuite/integration-arquillian/tests/adapters/pom.xml
index 1e4fdfa..ee81124 100644
--- a/testsuite/integration-arquillian/tests/adapters/pom.xml
+++ b/testsuite/integration-arquillian/tests/adapters/pom.xml
@@ -19,6 +19,8 @@
         <app.server.management.port.jmx>10199</app.server.management.port.jmx>
         <adapter.test.props>-Dapp.server.base.url=http://localhost:${app.server.http.port} -Dmy.host.name=localhost</adapter.test.props>
         <exclude.adapters>-</exclude.adapters>
+        
+        <arquillian.xml.stylesheet>src/main/xslt/arquillian.xsl</arquillian.xml.stylesheet>
     </properties>
     
     <build>
@@ -66,7 +68,13 @@
                                         <includes>
                                             <include>arquillian.xml</include>
                                         </includes>
-                                        <stylesheet>src/main/xslt/arquillian.xsl</stylesheet>
+                                        <stylesheet>${arquillian.xml.stylesheet}</stylesheet>
+                                        <parameters>
+                                            <parameter>
+                                                <name>keycloak.version</name>
+                                                <value>${project.version}</value>
+                                            </parameter>
+                                        </parameters>
                                         <outputDir>${project.build.directory}/dependency</outputDir>
                                     </transformationSet>
                                 </transformationSets>
diff --git a/testsuite/integration-arquillian/tests/adapters/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/WildflyDemoExampleAdapterTest.java b/testsuite/integration-arquillian/tests/adapters/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/WildflyDemoExampleAdapterTest.java
new file mode 100644
index 0000000..3f1aa7d
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/adapters/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/WildflyDemoExampleAdapterTest.java
@@ -0,0 +1,14 @@
+package org.keycloak.testsuite.adapter.example;
+
+import org.keycloak.testsuite.arquillian.annotation.AdapterLibsLocationProperty;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+@AppServerContainer("app-server-wildfly")
+@AdapterLibsLocationProperty("adapter.libs.wildfly")
+public class WildflyDemoExampleAdapterTest extends AbstractDemoExampleAdapterTest {
+
+}
\ 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 6317df8..5c409ab 100644
--- a/testsuite/integration-arquillian/tests/base/pom.xml
+++ b/testsuite/integration-arquillian/tests/base/pom.xml
@@ -15,7 +15,18 @@
         <exclude.console>-</exclude.console>
         <exclude.account>-</exclude.account>
     </properties>
-
+	<dependencies>
+	    <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-util-embedded-ldap</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>bouncycastle</groupId>
+                    <artifactId>bcprov-jdk15</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+	</dependencies>
     <build>
         <plugins>
             <plugin>
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/admin/ApiUtil.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/admin/ApiUtil.java
index a196fc7..78c8f6f 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/admin/ApiUtil.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/admin/ApiUtil.java
@@ -17,21 +17,22 @@
  */
 package org.keycloak.testsuite.admin;
 
+import org.jboss.logging.Logger;
+import org.keycloak.admin.client.resource.ClientResource;
 import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.admin.client.resource.UserResource;
 import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
 
 import javax.ws.rs.core.Response;
 import java.net.URI;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import org.jboss.logging.Logger;
-import org.keycloak.admin.client.resource.ClientResource;
-import org.keycloak.admin.client.resource.UserResource;
-import org.keycloak.representations.idm.CredentialRepresentation;
+
 import static org.keycloak.representations.idm.CredentialRepresentation.PASSWORD;
-import org.keycloak.representations.idm.RoleRepresentation;
-import org.keycloak.representations.idm.UserRepresentation;
 
 /**
  * Created by st on 28.05.15.
@@ -58,6 +59,15 @@ public class ApiUtil {
         return null;
     }
 
+    public static ClientResource findClientResourceByName(RealmResource realm, String name) {
+        for (ClientRepresentation c : realm.clients().findAll()) {
+            if (c.getName().equals(name)) {
+                return realm.clients().get(c.getId());
+            }
+        }
+        return null;
+    }
+
     public static ClientRepresentation findClientByClientId(RealmResource realm, String clientId) {
         ClientRepresentation client = null;
         for (ClientRepresentation c : realm.clients().findAll()) {
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/ContainersTestEnricher.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/ContainersTestEnricher.java
index 257fb55..9b68c43 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/ContainersTestEnricher.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/ContainersTestEnricher.java
@@ -1,7 +1,13 @@
 package org.keycloak.testsuite.arquillian;
 
+import java.io.File;
+import java.io.IOException;
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.util.LinkedList;
+import org.apache.commons.io.FileUtils;
+import org.jboss.arquillian.container.spi.Container;
+import org.jboss.arquillian.container.spi.ContainerRegistry;
 import org.jboss.arquillian.container.spi.event.StartSuiteContainers;
 import org.jboss.arquillian.container.spi.event.StopSuiteContainers;
 import org.jboss.arquillian.container.test.api.ContainerController;
@@ -33,13 +39,16 @@ import static org.keycloak.testsuite.auth.page.AuthRealm.MASTER;
 public class ContainersTestEnricher {
 
     protected final Logger log = Logger.getLogger(this.getClass());
-    
+
     @Inject
     private Instance<ContainerController> containerController;
 
     @Inject
+    private Instance<ContainerRegistry> containerRegistry;
+
+    @Inject
     private Event<StopSuiteContainers> stopSuiteContainers;
-    
+
     private String appServerQualifier;
 
     private static final String AUTH_SERVER_CONTAINER_PROPERTY = "auth.server.container";
@@ -62,24 +71,78 @@ public class ContainersTestEnricher {
     private InstanceProducer<OAuthClient> oauthClient;
 
     private ContainerController controller;
+    private LinkedList<Container> containers;
 
     private final boolean migrationTests = System.getProperty("migration", "false").equals("true");
     private boolean alreadyStopped = false;
+    private boolean init = false;
+
+    private void init() {
+        if (!init) {
+            containers = new LinkedList(containerRegistry.get().getContainers());
+        }
+        init = true;
+    }
 
+    /*
+     * non-javadoc
+     *
+     * Before starting suite containers. Initialization of containers is done 
+     * (only once during class life cycle)
+     */
     public void startSuiteContainers(@Observes(precedence = 1) StartSuiteContainers event) {
+        init();
         if (migrationTests) {
-            log.info("\n### Starting keycloak with previous version ###\n");
+            log.info("\n\n### Starting keycloak " + System.getProperty("version", "- previous") + " ###\n");
         }
     }
 
-    public void stopMigrationContainer(@Observes AfterStart event) {
+    /*
+     * non-javadoc
+     *
+     * After start container. Server logs are checked (in case jboss based container).
+     * In case of migration scenario: previous container is stopped.
+     */
+    public void afterStart(@Observes AfterStart event) throws IOException {
+        if (System.getProperty("check.server.log", "true").equals("true")) {
+            checkServerLog();
+        }
+
         if (migrationTests && !alreadyStopped) {
-            log.info("\n### Stopping keycloak with previous version ###\n");
+            log.info("\n\n### Stopping keycloak " + System.getProperty("version", "- previous") + " ###\n");
             stopSuiteContainers.fire(new StopSuiteContainers());
+            log.info("\n\n### Starting keycloak current version ###\n");
         }
         alreadyStopped = true;
     }
-    
+
+    /*
+     * non-javadoc
+     *
+     * check server logs (in case jboss based container) whether there are no ERRORs or SEVEREs
+     */
+    private void checkServerLog() throws IOException {
+        Container container = containers.removeFirst();
+        if (container.getName().equals("auth-server-wildfly")
+                || container.getName().matches("auth-server-eap.")) {
+            String jbossHomePath = container.getContainerConfiguration().getContainerProperties().get("jbossHome");
+            log.debug("jbossHome: " + jbossHomePath + "\n");
+
+            String serverLogContent = FileUtils.readFileToString(new File(jbossHomePath + "/standalone/log/server.log"));
+
+            boolean containsError
+                    = serverLogContent.contains("ERROR")
+                    || serverLogContent.contains("SEVERE")
+                    || serverLogContent.contains("Exception ");
+            //There is expected string "Exception" in server log: Adding provider 
+            //singleton org.keycloak.services.resources.ModelExceptionMapper
+
+            if (containsError) {
+                throw new RuntimeException(container.getName() + ": Server log contains ERROR.");
+            }
+        }
+    }
+
     public void beforeSuite(@Observes BeforeSuite event) {
         suiteContext.set(new SuiteContext());
     }
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 73583cf..d23d4b5 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
@@ -9,7 +9,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.jira.JiraTestExecutionDecider;
+import org.keycloak.testsuite.arquillian.migration.MigrationTestExecutionDecider;
 import org.keycloak.testsuite.arquillian.undertow.CustomUndertowContainer;
 
 /**
@@ -36,7 +36,7 @@ public class KeycloakArquillianExtension implements LoadableExtension {
                 .service(DeployableContainer.class, CustomUndertowContainer.class);
 
         builder
-                .service(TestExecutionDecider.class, JiraTestExecutionDecider.class);
+                .service(TestExecutionDecider.class, MigrationTestExecutionDecider.class);
 
         builder
                 .override(ResourceProvider.class, URLResourceProvider.class, URLProvider.class)
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/migration/Migration.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/migration/Migration.java
new file mode 100644
index 0000000..791e588
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/migration/Migration.java
@@ -0,0 +1,39 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ * 
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.arquillian.migration;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import java.lang.annotation.Target;
+
+/**
+ *
+ * @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
+ */
+@Documented
+@Retention(RUNTIME)
+@Target({ElementType.METHOD})
+public @interface Migration {
+    String versionFrom();
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/migration/MigrationTestExecutionDecider.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/migration/MigrationTestExecutionDecider.java
new file mode 100644
index 0000000..bfac24b
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/migration/MigrationTestExecutionDecider.java
@@ -0,0 +1,61 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ * 
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.arquillian.migration;
+
+import java.lang.reflect.Method;
+import org.jboss.arquillian.test.spi.execution.ExecutionDecision;
+import org.jboss.arquillian.test.spi.execution.TestExecutionDecider;
+
+/**
+ * @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
+ */
+public class MigrationTestExecutionDecider implements TestExecutionDecider {
+
+    @Override
+    public ExecutionDecision decide(Method method) {
+        
+        boolean migrationTest = "true".equals(System.getProperty("migration", "false"));
+        Migration migrationAnnotation = method.getAnnotation(Migration.class);
+        
+        if (migrationTest && migrationAnnotation != null) {
+            String versionFrom = migrationAnnotation.versionFrom();
+            String version = System.getProperty("version");
+            
+
+            if (version.equals(versionFrom)) {
+                return ExecutionDecision.execute();
+            } else {
+                return ExecutionDecision.dontExecute(method.getName() + "doesn't fit with migration version.");
+            }
+        }
+        if ((migrationTest && migrationAnnotation == null) || (!migrationTest && migrationAnnotation != null)) {
+            return ExecutionDecision.dontExecute("Migration test and no migration annotation or no migration test and migration annotation");
+        }
+        return ExecutionDecision.execute();
+    }
+
+    @Override
+    public int precedence() {
+        return 1;
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/account/Applications.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/account/Applications.java
index 5623198..7e50272 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/account/Applications.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/account/Applications.java
@@ -17,14 +17,16 @@
  */
 package org.keycloak.testsuite.auth.page.account;
 
-import java.util.List;
-import javax.ws.rs.core.UriBuilder;
+import org.openqa.selenium.By;
 import org.openqa.selenium.WebElement;
 import org.openqa.selenium.support.FindBy;
 
+import javax.ws.rs.core.UriBuilder;
+import java.util.List;
+
 /**
- *
  * @author Petr Mensik
+ * @author mhajas
  */
 public class Applications extends AccountManagement {
 
@@ -39,26 +41,42 @@ public class Applications extends AccountManagement {
     @FindBy(xpath = XPATH_APP_TABLE)
     protected WebElement appTable;
 
-    @FindBy(xpath = XPATH_APP_TABLE + "//a")
-    protected List<WebElement> applicationLinks;
-    
+    @FindBy(xpath = XPATH_APP_TABLE + "//tr")
+    private List<WebElement> applicationRows;
+
     public boolean containsApplication(String application) {
-        boolean contains = false;
-        for (WebElement appLink : applicationLinks) {
-            if (appLink.getText().equals(application)) {
-                contains = true;
-                break;
-            }
-        }
-        return contains;
+        return getRowForLinkText(application) != null;
     }
-    
+
     public void clickApplication(String application) {
-        for (WebElement appLink : applicationLinks) {
-            if (appLink.getText().equals(application)) {
-                appLink.click();
+        WebElement row = getRowForLinkText(application);
+        if (row == null) {
+            log.error("Application: " + application + " doesn't exist");
+            throw new IllegalArgumentException("Application: " + application + " doesn't exist");
+        }
+
+        row.findElement(By.xpath(".//a")).click();
+    }
+
+    public void revokeGrantForApplication(String application) {
+        WebElement row = getRowForLinkText(application);
+        if (row == null) {
+            log.error("Application: " + application + " doesn't exist");
+            throw new IllegalArgumentException("Application: " + application + " doesn't exist");
+        }
+
+        row.findElement(By.xpath("//button[@id='revoke-" + application + "']")).click();
+    }
+
+    private WebElement getRowForLinkText(String appLink) {
+        for (WebElement appRow : applicationRows) {
+            if (appRow.findElement(By.xpath(".//td")).getText().equals(appLink)) {
+                return appRow;
             }
         }
+
+        return null;
     }
 
+
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/AuthRealm.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/AuthRealm.java
index e8a435c..f6ba6b4 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/AuthRealm.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/AuthRealm.java
@@ -1,15 +1,16 @@
 package org.keycloak.testsuite.auth.page;
 
+import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
 import org.keycloak.testsuite.auth.page.login.PageWithLoginUrl;
-import java.net.URI;
+
 import javax.ws.rs.core.UriBuilder;
-import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
+import java.net.URI;
 
 /**
  * Keycloak realm.
- * 
+ * <p>
  * URL: http://localhost:${auth.server.http.port}/auth/realms/{authRealm}
- * 
+ *
  * @author tkyjovsk
  */
 public class AuthRealm extends AuthServer implements PageWithLoginUrl {
@@ -22,7 +23,7 @@ public class AuthRealm extends AuthServer implements PageWithLoginUrl {
     public static final String EXAMPLE = "example";
 
     public static final String ADMIN = "admin";
-    
+
     public AuthRealm() {
         setUriParameter(AUTH_REALM, MASTER);
     }
@@ -46,7 +47,6 @@ public class AuthRealm extends AuthServer implements PageWithLoginUrl {
     }
 
     /**
-     *
      * @return OIDC Login URL for authRealm
      */
     @Override
@@ -55,4 +55,9 @@ public class AuthRealm extends AuthServer implements PageWithLoginUrl {
                 .build(getAuthRealm());
     }
 
+    public URI getOIDCLogoutUrl() {
+        return OIDCLoginProtocolService.logoutUrl(UriBuilder.fromPath(getAuthRoot()))
+                .build(getAuthRealm());
+    }
+
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/OAuthGrant.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/OAuthGrant.java
new file mode 100644
index 0000000..e328014
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/OAuthGrant.java
@@ -0,0 +1,50 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.auth.page.login;
+
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class OAuthGrant extends LoginActions {
+
+    @FindBy(css = "input[name=\"accept\"]")
+    private WebElement acceptButton;
+    @FindBy(css = "input[name=\"cancel\"]")
+    private WebElement cancelButton;
+
+
+    public void accept() {
+        acceptButton.click();
+    }
+
+    public void cancel() {
+        cancelButton.click();
+    }
+
+    @Override
+    public boolean isCurrent() {
+        return driver.getTitle().equals("OAuth Grant");
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/AdminConsoleRealm.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/AdminConsoleRealm.java
index 4fd57ba..852133b 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/AdminConsoleRealm.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/AdminConsoleRealm.java
@@ -57,7 +57,7 @@ public class AdminConsoleRealm extends AdminConsoleRealmsRoot {
         private WebElement rolesLink;
         @FindBy(partialLinkText = "Identity Providers")
         private WebElement identityProvidersLink;
-        @FindBy(partialLinkText = "User Feferation")
+        @FindBy(partialLinkText = "User Federation")
         private WebElement userFederationLink;
         @FindBy(partialLinkText = "Authentication")
         private WebElement authenticationLink;
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/Authentication.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/Authentication.java
index 5cd74bf..858de40 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/Authentication.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/Authentication.java
@@ -3,6 +3,7 @@ package org.keycloak.testsuite.console.page.authentication;
 import org.keycloak.testsuite.console.page.AdminConsoleRealm;
 import org.openqa.selenium.WebElement;
 import org.openqa.selenium.support.FindBy;
+import static org.keycloak.testsuite.util.WaitUtils.*;
 
 /**
  * @author tkyjovsk
@@ -13,6 +14,32 @@ public class Authentication extends AdminConsoleRealm {
     @FindBy(xpath = "//h1[text()='Authentication']/..")
     private AuthenticationTabs authenticationTabs;
 
+    @FindBy(xpath = "//div[contains(@class, 'alert-danger')]")
+    private WebElement error;
+    
+    @FindBy(xpath = "//div[contains(@class, 'alert-success')]")
+    private WebElement success;
+    
+    @FindBy(xpath = "//button[@class='close']/span")
+    private WebElement close;
+    
+    public String getSuccessMessage() {
+        waitAjaxForElement(success);
+        return success.getText();
+    }
+    
+    public String getErrorMessage() {
+        waitAjaxForElement(error);
+        return error.getText();
+    }
+    
+    public void closeNotification() {
+        if (close.isDisplayed()) {
+            close.click();
+        }
+        waitAjaxForElementNotVisible(close);
+    }
+    
     public AuthenticationTabs tabs() {
         return authenticationTabs;
     }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/bindings/Bindings.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/bindings/Bindings.java
new file mode 100644
index 0000000..12bb9c1
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/bindings/Bindings.java
@@ -0,0 +1,44 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ * 
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.console.page.authentication.bindings;
+
+import org.jboss.arquillian.graphene.page.Page;
+import org.keycloak.testsuite.console.page.authentication.Authentication;
+
+/**
+ *
+ * @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
+ */
+public class Bindings extends Authentication {
+
+    @Override
+    public String getUriFragment() {
+        return super.getUriFragment() + "/flow-bindings";
+    }
+
+    @Page
+    private BindingsForm form;
+    
+    public BindingsForm form() {
+        return form;
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/bindings/BindingsForm.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/bindings/BindingsForm.java
new file mode 100644
index 0000000..d3e2f66
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/bindings/BindingsForm.java
@@ -0,0 +1,95 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ * 
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.console.page.authentication.bindings;
+
+import org.keycloak.testsuite.console.page.authentication.bindings.BindingsForm.BindingsSelect;
+import org.keycloak.testsuite.page.Form;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.ui.Select;
+
+/**
+ *
+ * @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
+ */
+public class BindingsForm extends Form {
+    
+    @FindBy(id = "browser")
+    private Select browser;
+
+    @FindBy(id = "registration")
+    private Select registration;
+
+    @FindBy(id = "grant")
+    private Select grant;
+
+    @FindBy(id = "resetCredentials")
+    private Select resetCredentials;
+
+    @FindBy(id = "clientAuthentication")
+    private Select clientAuthentication;
+
+    public void select(BindingsSelect select, BindingsOption option) {
+        switch (select) {
+            case BROWSER:
+                browser.selectByVisibleText(option.getName());
+                break;
+            case REGISTRATION:
+                registration.selectByVisibleText(option.getName());
+                break;
+            case DIRECT_GRANT:
+                grant.selectByVisibleText(option.getName());
+                break;
+            case RESET_CREDENTIALS:
+                resetCredentials.selectByVisibleText(option.getName());
+                break;
+            case CLIENT_AUTHENTICATION:
+                clientAuthentication.selectByVisibleText(option.getName());
+                break;
+        }
+    }
+
+    public enum BindingsSelect {
+        BROWSER, 
+        REGISTRATION, 
+        DIRECT_GRANT,
+        RESET_CREDENTIALS,
+        CLIENT_AUTHENTICATION;
+    }
+    
+    public enum BindingsOption {
+
+        DIRECT_GRANT("direct grant"), 
+        REGISTRATION("registration"), 
+        BROWSER("browser"),
+        RESET_CREDENTIALS("reset credentials");
+
+        private final String name;
+
+        private BindingsOption(String name) {
+            this.name = name;
+        }
+
+        public String getName() {
+            return name;
+        }
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/otppolicy/OTPPolicy.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/otppolicy/OTPPolicy.java
new file mode 100644
index 0000000..814ae54
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/otppolicy/OTPPolicy.java
@@ -0,0 +1,44 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ * 
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.console.page.authentication.otppolicy;
+
+import org.jboss.arquillian.graphene.page.Page;
+import org.keycloak.testsuite.console.page.authentication.Authentication;
+
+/**
+ *
+ * @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
+ */
+public class OTPPolicy extends Authentication {
+
+    @Override
+    public String getUriFragment() {
+        return super.getUriFragment() + "/otp-policy";
+    }
+    
+    @Page
+    private OTPPolicyForm form;
+    
+    public OTPPolicyForm form() {
+        return form;
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/otppolicy/OTPPolicyForm.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/otppolicy/OTPPolicyForm.java
new file mode 100644
index 0000000..1e88cb3
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/otppolicy/OTPPolicyForm.java
@@ -0,0 +1,119 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ * 
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.console.page.authentication.otppolicy;
+
+import org.keycloak.testsuite.page.Form;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.ui.Select;
+
+/**
+ *
+ * @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
+ */
+public class OTPPolicyForm extends Form {
+    
+    @FindBy(id = "type")
+    private Select otpType;
+    
+    @FindBy(id = "alg")
+    private Select otpHashAlg;
+    
+    @FindBy(id = "digits")
+    private Select digits;
+    
+    @FindBy(id = "lookAhead")
+    private WebElement lookAhead;
+    
+    @FindBy(id = "period")
+    private WebElement period;
+    
+    @FindBy(id = "counter")
+    private WebElement counter;
+    
+    public void setValues(OTPType otpType, OTPHashAlg otpHashAlg, Digits digits, String lookAhead, String periodOrCounter) {
+        this.otpType.selectByValue(otpType.getName());
+        this.otpHashAlg.selectByValue(otpHashAlg.getName());
+        this.digits.selectByValue(digits.getName());
+        setInputValue(this.lookAhead, lookAhead);
+        
+        switch (otpType) {
+            case TIME_BASED:
+                setInputValue(period, periodOrCounter);
+                break;
+            case COUNTER_BASED:
+                setInputValue(counter, periodOrCounter);
+                break;
+        }
+        save();
+    }
+    
+    public enum OTPType {
+
+        TIME_BASED("totp"),
+        COUNTER_BASED("hotp");
+
+        private final String name;
+
+        private OTPType(String name) {
+            this.name = name;
+        }
+
+        public String getName() {
+            return name;
+        }
+    }
+    
+    public enum OTPHashAlg {
+
+        SHA1("HmacSHA1"),
+        SHA256("HmacSHA256"),
+        SHA512("HmacSHA512");
+
+        private final String name;
+
+        private OTPHashAlg(String name) {
+            this.name = name;
+        }
+
+        public String getName() {
+            return name;
+        }
+    }
+    
+    public enum Digits {
+
+        EMPTY("? number:6 ?"),
+        SIX("6"),
+        EIGHT("8");
+
+        private final String name;
+
+        private Digits(String name) {
+            this.name = name;
+        }
+
+        public String getName() {
+            return name;
+        }
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/RequiredActions.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/RequiredActions.java
index 0ea6af2..b6f182a 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/RequiredActions.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/RequiredActions.java
@@ -7,11 +7,17 @@ import org.openqa.selenium.support.FindBy;
 /**
  * @author tkyjovsk
  * @author mhajas
+ * @author Vaclav Muzikar <vmuzikar@redhat.com>
  */
 public class RequiredActions extends Authentication {
 
-    public final static String ENABLED = "enabled";
-    public final static String DEFAULT_ACTION = "defaultAction";
+    public final static String ENABLED = ".enabled";
+    public final static String DEFAULT = ".defaultAction";
+    public final static String CONFIGURE_TOTP = "CONFIGURE_TOTP";
+    public final static String UPDATE_PROFILE = "UPDATE_PROFILE";
+    public final static String TERMS_AND_CONDITIONS = "terms_and_conditions";
+    public final static String UPDATE_PASSWORD = "UPDATE_PASSWORD";
+    public final static String VERIFY_EMAIL = "VERIFY_EMAIL";
 
     @FindBy(tagName = "table")
     private WebElement requiredActionTable;
@@ -21,51 +27,59 @@ public class RequiredActions extends Authentication {
         return super.getUriFragment() + "/required-actions";
     }
 
-    private void setRequiredActionValue(String row, String column, boolean value) {
-        WebElement checkbox = requiredActionTable.findElement(By.xpath("//td[text()='" + row + "']/..//input[@ng-model='requiredAction." + column + "']"));
+    private void setRequiredActionValue(String id, boolean value) {
+        WebElement checkbox = requiredActionTable.findElement(By.id(id));
 
         if (checkbox.isSelected() != value) {
             checkbox.click();
         }
     }
 
+    private void setRequiredActionEnabledValue(String id, boolean value) {
+        setRequiredActionValue(id + ENABLED, value);
+    }
+
+    private void setRequiredActionDefaultValue(String id, boolean value) {
+        setRequiredActionValue(id + DEFAULT, value);
+    }
+
     public void setTermsAndConditionEnabled(boolean value) {
-        setRequiredActionValue("Terms and Conditions", ENABLED, value);
+        setRequiredActionEnabledValue(TERMS_AND_CONDITIONS, value);
     }
 
     public void setTermsAndConditionDefaultAction(boolean value) {
-        setRequiredActionValue("Terms and Conditions", DEFAULT_ACTION, value);
+        setRequiredActionDefaultValue(TERMS_AND_CONDITIONS, value);
     }
 
     public void setVerifyEmailEnabled(boolean value) {
-        setRequiredActionValue("Verify Email", ENABLED, value);
+        setRequiredActionEnabledValue(VERIFY_EMAIL, value);
     }
 
     public void setVerifyEmailDefaultAction(boolean value) {
-        setRequiredActionValue("Verify Email", DEFAULT_ACTION, value);
+        setRequiredActionDefaultValue(VERIFY_EMAIL, value);
     }
 
     public void setUpdatePasswordEnabled(boolean value) {
-        setRequiredActionValue("Update Password", ENABLED, value);
+        setRequiredActionEnabledValue(UPDATE_PASSWORD, value);
     }
 
     public void setUpdatePasswordDefaultAction(boolean value) {
-        setRequiredActionValue("Update Password", DEFAULT_ACTION, value);
+        setRequiredActionDefaultValue(UPDATE_PASSWORD, value);
     }
 
     public void setConfigureTotpEnabled(boolean value) {
-        setRequiredActionValue("Configure Totp", ENABLED, value);
+        setRequiredActionEnabledValue(CONFIGURE_TOTP, value);
     }
 
     public void setConfigureTotpDefaultAction(boolean value) {
-        setRequiredActionValue("Configure Totp", DEFAULT_ACTION, value);
+        setRequiredActionDefaultValue(CONFIGURE_TOTP, value);
     }
 
     public void setUpdateProfileEnabled(boolean value) {
-        setRequiredActionValue("Update Profile", ENABLED, value);
+        setRequiredActionEnabledValue(UPDATE_PROFILE, value);
     }
 
     public void setUpdateProfileDefaultAction(boolean value) {
-        setRequiredActionValue("Update Profile", DEFAULT_ACTION, value);
+        setRequiredActionDefaultValue(UPDATE_PROFILE, value);
     }
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/ClientMappers.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/ClientMappers.java
index d56dca5..10fb7e5 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/ClientMappers.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/ClientMappers.java
@@ -1,14 +1,112 @@
 package org.keycloak.testsuite.console.page.clients;
 
+import org.keycloak.representations.idm.ProtocolMapperRepresentation;
+import org.keycloak.testsuite.console.page.fragment.DataTable;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  *
  * @author tkyjovsk
+ * @author Vaclav Muzikar <vmuzikar@redhat.com>
  */
 public class ClientMappers extends Client {
 
+    public static final String ADD_BUILTIN = "Add Builtin";
+
+    @FindBy(tagName = "table")
+    private ClientMapperTable table;
+
     @Override
     public String getUriFragment() {
         return super.getUriFragment() + "/mappers";
     }
 
+    public ClientMapperTable mapperTable() {
+        return table;
+    }
+
+    public class ClientMapperTable extends DataTable {
+
+        public List<ProtocolMapperRepresentation> searchMappings(String searchPattern) {
+            search(searchPattern);
+            return getMappingsFromRows();
+        }
+
+        public void createMapper() {
+            waitAjaxForBody();
+            clickHeaderLink(CREATE);
+        }
+
+        public void addBuiltin() {
+            waitAjaxForBody();
+            clickHeaderLink(ADD_BUILTIN);
+        }
+
+        public void clickMapper(String mapperName) {
+            waitAjaxForBody();
+            body().findElement(By.linkText(mapperName)).click();
+        }
+
+        public void clickMapper(ProtocolMapperRepresentation mapper) {
+            clickMapper(mapper.getName());
+        }
+
+        private void clickMapperActionButton(String mapperName, String buttonText) {
+            waitAjaxForBody();
+            clickRowActionButton(getRowByLinkText(mapperName), buttonText);
+        }
+
+        private void clickMapperActionButton(ProtocolMapperRepresentation mapper, String buttonName) {
+            clickMapperActionButton(mapper.getName(), buttonName);
+        }
+
+        public void editMapper(String mapperName) {
+            clickMapperActionButton(mapperName, EDIT);
+        }
+
+        public void editMapper(ProtocolMapperRepresentation mapper) {
+            clickMapperActionButton(mapper, EDIT);
+        }
+
+        public void deleteMapper(String mapperName) {
+            clickMapperActionButton(mapperName, DELETE);
+        }
+
+        public void deleteMapper(ProtocolMapperRepresentation mapper) {
+            clickMapperActionButton(mapper, DELETE);
+        }
+
+        public ProtocolMapperRepresentation getMappingFromRow(WebElement row) {
+            if (!row.isDisplayed()) {return null;} // Is that necessary?
+
+            ProtocolMapperRepresentation mappingsRepresentation = new ProtocolMapperRepresentation();
+            List<WebElement> cols = row.findElements(By.tagName("td"));
+
+
+            mappingsRepresentation.setName(cols.get(0).getText());
+            //mappingsRepresentation.setProtocol(cols.get(1).getText());
+            mappingsRepresentation.setProtocolMapper(cols.get(2).getText());
+
+            return mappingsRepresentation;
+        }
+
+        public List<ProtocolMapperRepresentation> getMappingsFromRows() {
+            List<ProtocolMapperRepresentation> mappings = new ArrayList<ProtocolMapperRepresentation>();
+
+            for (WebElement row : rows()) {
+                ProtocolMapperRepresentation mapperRepresentation = getMappingFromRow(row);
+                if (mapperRepresentation != null) {
+                    mappings.add(mapperRepresentation);
+                }
+            }
+
+            return mappings;
+        }
+    }
+
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/ClientSettingsForm.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/ClientSettingsForm.java
index 41cdf71..589e68e 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/ClientSettingsForm.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/ClientSettingsForm.java
@@ -1,15 +1,17 @@
 package org.keycloak.testsuite.console.page.clients;
 
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.testsuite.console.page.fragment.OnOffSwitch;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
 import java.util.ArrayList;
 import java.util.List;
-import org.keycloak.representations.idm.ClientRepresentation;
+
 import static org.keycloak.testsuite.auth.page.login.Login.OIDC;
 import static org.keycloak.testsuite.util.WaitUtils.pause;
-import org.openqa.selenium.WebElement;
-import org.openqa.selenium.support.FindBy;
 
 /**
- *
  * @author tkyjovsk
  */
 public class ClientSettingsForm extends CreateClientForm {
@@ -26,6 +28,9 @@ public class ClientSettingsForm extends CreateClientForm {
     @FindBy(xpath = ".//i[contains(@data-ng-click, 'deleteWebOrigin')]")
     private List<WebElement> deleteWebOriginIcons;
 
+    @FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='consentRequired']]")
+    private OnOffSwitch consentRequired;
+
     public void setBaseUrl(String baseUrl) {
         setInputValue(baseUrlInput, baseUrl);
     }
@@ -88,4 +93,8 @@ public class ClientSettingsForm extends CreateClientForm {
         return values;
     }
 
+    public void setConsentRequired(boolean value) {
+        consentRequired.setOn(value);
+    }
+
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/CreateClientMappers.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/CreateClientMappers.java
new file mode 100644
index 0000000..962e7a5
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/CreateClientMappers.java
@@ -0,0 +1,21 @@
+package org.keycloak.testsuite.console.page.clients;
+
+import org.jboss.arquillian.graphene.page.Page;
+import org.keycloak.testsuite.console.page.AdminConsoleCreate;
+
+/**
+ * @author Vaclav Muzikar <vmuzikar@redhat.com>
+ */
+public class CreateClientMappers extends AdminConsoleCreate {
+
+    @Page
+    private CreateClientMappersForm form;
+
+    public CreateClientMappers() {
+        setEntity("mappers");
+    }
+
+    public CreateClientMappersForm form() {
+        return form;
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/CreateClientMappersForm.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/CreateClientMappersForm.java
new file mode 100644
index 0000000..900b4d2
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/CreateClientMappersForm.java
@@ -0,0 +1,187 @@
+package org.keycloak.testsuite.console.page.clients;
+
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.keycloak.testsuite.console.page.fragment.OnOffSwitch;
+import org.keycloak.testsuite.page.Form;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.interactions.Actions;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.ui.Select;
+
+import java.util.List;
+
+/**
+ * @author Vaclav Muzikar <vmuzikar@redhat.com>
+ *
+ * TODO: SAML
+ */
+public class CreateClientMappersForm extends Form {
+
+    // Mappers types
+    public static final String HARDCODED_ROLE = "Hardcoded Role";
+    public static final String HARDCODED_CLAIM = "Hardcoded claim";
+    public static final String USER_SESSION_NOTE = "User Session Note";
+    public static final String ROLE_NAME_MAPPER = "Role Name Mapper";
+    public static final String USER_ADDRESS = "User Address";
+    public static final String USERS_FULL_NAME = "User's full name";
+    public static final String USER_ATTRIBUTE = "User Attribute";
+    public static final String USER_PROPERTY = "User Property";
+
+    @FindBy(id = "name")
+    private WebElement nameElement;
+
+    @FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='consentRequired']]")
+    private OnOffSwitch consentRequiredSwitch;
+
+    @FindBy(id = "consentText")
+    private WebElement consentTextElement;
+
+    @FindBy(id = "mapperTypeCreate")
+    private Select mapperTypeSelect;
+
+    @FindBy(xpath = ".//div[@properties='mapperType.properties']//label[text()='Property']//following-sibling::node()//input[@type='text']")
+    private WebElement propertyInput;
+
+    @FindBy(xpath = ".//div[@properties='mapperType.properties']//label[text()='User Attribute']//following-sibling::node()//input[@type='text']")
+    private WebElement userAttributeInput;
+
+    @FindBy(xpath = ".//div[@properties='mapperType.properties']//label[text()='User Session Note']//following-sibling::node()//input[@type='text']")
+    private WebElement userSessionNoteInput;
+
+    @FindBy(xpath = ".//div[@properties='mapperType.properties']//label[text()='Multivalued']//following-sibling::node()//div[@class='onoffswitch']")
+    private OnOffSwitch multivaluedInput;
+
+    @FindBy(xpath = ".//div[@properties='mapperType.properties']//label[text()='Role']//following-sibling::node()//input[@type='text']")
+    private WebElement roleInput;
+
+    @FindBy(xpath = ".//div[@properties='mapperType.properties']//label[text()='New Role Name']//following-sibling::node()//input[@type='text']")
+    private WebElement newRoleInput;
+
+    @FindBy(xpath = ".//div[@properties='mapperType.properties']//label[text()='Token Claim Name']//following-sibling::node()//input[@type='text']")
+    private WebElement tokenClaimNameInput;
+
+    @FindBy(xpath = ".//div[@properties='mapperType.properties']//label[text()='Claim value']//following-sibling::node()//input[@type='text']")
+    private WebElement tokenClaimValueInput;
+
+    @FindBy(xpath = ".//div[@properties='mapperType.properties']//label[text()='Claim JSON Type']//following-sibling::node()//select")
+    private Select claimJSONTypeInput;
+
+    @FindBy(xpath = ".//div[@properties='mapperType.properties']//label[text()='Add to ID token']//following-sibling::node()//div[@class='onoffswitch']")
+    private OnOffSwitch addToIDTokenInput;
+
+    @FindBy(xpath = ".//div[@properties='mapperType.properties']//label[text()='Add to access token']//following-sibling::node()//div[@class='onoffswitch']")
+    private OnOffSwitch addToAccessTokenInput;
+
+    public boolean isConsentRequired() {
+        return consentRequiredSwitch.isOn();
+    }
+
+    public void setConsentRequired(boolean consentRequired) {
+        consentRequiredSwitch.setOn(consentRequired);
+    }
+
+    public String getConsentText() {
+        return getInputValue(consentTextElement);
+    }
+
+    public void setConsentText(String consentText) {
+        setInputValue(consentTextElement, consentText);
+    }
+
+    public String getMapperType() {
+        return mapperTypeSelect.getFirstSelectedOption().getText();
+    }
+
+    public void setMapperType(String type) {
+        mapperTypeSelect.selectByVisibleText(type);
+    }
+    
+    public String getProperty() {
+        return getInputValue(propertyInput);
+    }
+    
+    public void setProperty(String value) {
+        setInputValue(propertyInput, value);
+    }
+
+    public String getUserAttribute() {
+        return getInputValue(userAttributeInput);
+    }
+
+    public void setUserAttribute(String value) {
+        setInputValue(userAttributeInput, value);
+    }
+
+    public String getUserSessionNote() {
+        return getInputValue(userSessionNoteInput);
+    }
+
+    public void setUserSessionNote(String value) {
+        setInputValue(userSessionNoteInput, value);
+    }
+
+    public boolean isMultivalued() {
+        return multivaluedInput.isOn();
+    }
+
+    public void setMultivalued(boolean value) {
+        multivaluedInput.setOn(value);
+    }
+
+    public String getRole() {
+        return getInputValue(roleInput);
+    }
+
+    public void setRole(String value) {
+        setInputValue(roleInput, value);
+    }
+
+    public String getNewRole() {
+        return getInputValue(newRoleInput);
+    }
+
+    public void setNewRole(String value) {
+        setInputValue(newRoleInput, value);
+    }
+
+    public String getTokenClaimName() {
+        return getInputValue(tokenClaimNameInput);
+    }
+
+    public void setTokenClaimName(String value) {
+        setInputValue(tokenClaimNameInput, value);
+    }
+
+    public String getTokenClaimValue() {
+        return getInputValue(tokenClaimValueInput);
+    }
+
+    public void setTokenClaimValue(String value) {
+        setInputValue(tokenClaimValueInput, value);
+    }
+
+    public String getClaimJSONType() {
+        return claimJSONTypeInput.getFirstSelectedOption().getText();
+    }
+
+    public void setClaimJSONType(String value) {
+        claimJSONTypeInput.selectByVisibleText(value);
+    }
+
+    public boolean isAddToIDToken() {
+        return addToIDTokenInput.isOn();
+    }
+
+    public void setAddToIDToken(boolean value) {
+        addToIDTokenInput.setOn(value);
+    }
+
+    public boolean isAddToAccessToken() {
+        return addToAccessTokenInput.isOn();
+    }
+
+    public void setAddToAccessToken(boolean value) {
+        addToAccessTokenInput.setOn(value);
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/federation/CreateKerberosUserProvider.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/federation/CreateKerberosUserProvider.java
new file mode 100644
index 0000000..6347392
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/federation/CreateKerberosUserProvider.java
@@ -0,0 +1,28 @@
+package org.keycloak.testsuite.console.page.federation;
+
+import org.jboss.arquillian.graphene.page.Page;
+import org.keycloak.testsuite.console.page.AdminConsoleCreate;
+
+/**
+ *
+ * @author pdrozd
+ */
+public class CreateKerberosUserProvider extends AdminConsoleCreate {
+
+    @Page
+    private KerberosUserProviderForm form;
+
+    public CreateKerberosUserProvider() {
+        setEntity("user-federation");
+    }
+
+    @Override
+    public String getUriFragment() {
+        return super.getUriFragment() + "/providers/kerberos";
+    }
+
+    public KerberosUserProviderForm form() {
+        return form;
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/federation/CreateLdapUserProvider.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/federation/CreateLdapUserProvider.java
index 4dc47f9..13ba716 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/federation/CreateLdapUserProvider.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/federation/CreateLdapUserProvider.java
@@ -1,5 +1,6 @@
 package org.keycloak.testsuite.console.page.federation;
 
+import org.jboss.arquillian.graphene.page.Page;
 import org.keycloak.testsuite.console.page.AdminConsoleCreate;
 
 /**
@@ -8,6 +9,9 @@ import org.keycloak.testsuite.console.page.AdminConsoleCreate;
  */
 public class CreateLdapUserProvider extends AdminConsoleCreate {
 
+    @Page
+    private LdapUserProviderForm form;
+
     public CreateLdapUserProvider() {
         setEntity("user-federation");
     }
@@ -17,4 +21,7 @@ public class CreateLdapUserProvider extends AdminConsoleCreate {
         return super.getUriFragment() + "/providers/ldap";
     }
 
+    public LdapUserProviderForm form() {
+        return form;
+    }
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/federation/KerberosUserProviderForm.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/federation/KerberosUserProviderForm.java
new file mode 100644
index 0000000..1fb068f
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/federation/KerberosUserProviderForm.java
@@ -0,0 +1,81 @@
+package org.keycloak.testsuite.console.page.federation;
+
+import static org.keycloak.testsuite.util.WaitUtils.waitGuiForElement;
+
+import org.keycloak.testsuite.console.page.fragment.OnOffSwitch;
+import org.keycloak.testsuite.page.Form;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.ui.Select;
+
+/**
+ * @author pdrozd
+ */
+public class KerberosUserProviderForm extends Form {
+
+    @FindBy(id = "consoleDisplayName")
+    private WebElement consoleDisplayNameInput;
+
+    @FindBy(id = "priority")
+    private WebElement priorityInput;
+
+    @FindBy(id = "kerberosRealm")
+    private WebElement kerberosRealmInput;
+
+    @FindBy(id = "serverPrincipal")
+    private WebElement serverPrincipalInput;
+
+    @FindBy(id = "keyTab")
+    private WebElement keyTabInput;
+
+    @FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='debug']]")
+    private OnOffSwitch debug;
+
+    @FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='allowPasswordAuthentication']]")
+    private OnOffSwitch allowPwdAuth;
+
+    @FindBy(id = "editMode")
+    private Select editModeSelect;
+
+    @FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='updateProfileFirstLogin']]")
+    private OnOffSwitch updateProfileFirstLogin;
+
+    public void setConsoleDisplayNameInput(String name) {
+        setInputValue(consoleDisplayNameInput, name);
+    }
+
+    public void setPriorityInput(Integer priority) {
+        setInputValue(priorityInput, String.valueOf(priority));
+    }
+
+    public void setKerberosRealmInput(String kerberosRealm) {
+        waitGuiForElement(By.id("kerberosRealm"));
+        setInputValue(kerberosRealmInput, kerberosRealm);
+    }
+
+    public void setServerPrincipalInput(String serverPrincipal) {
+        setInputValue(serverPrincipalInput, serverPrincipal);
+    }
+
+    public void setKeyTabInput(String keyTab) {
+        setInputValue(keyTabInput, keyTab);
+    }
+
+    public void setDebugEnabled(boolean debugEnabled) {
+        this.debug.setOn(debugEnabled);
+    }
+
+    public void setAllowPasswordAuthentication(boolean enabled) {
+        allowPwdAuth.setOn(enabled);
+    }
+
+    public void selectEditMode(String mode) {
+        waitGuiForElement(By.id("editMode"));
+        editModeSelect.selectByVisibleText(mode);
+    }
+
+    public void setUpdateProfileFirstLogin(boolean enabled) {
+        updateProfileFirstLogin.setOn(enabled);
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/federation/LdapUserProviderForm.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/federation/LdapUserProviderForm.java
index a9b8882..3acc5ec 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/federation/LdapUserProviderForm.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/federation/LdapUserProviderForm.java
@@ -1,5 +1,8 @@
 package org.keycloak.testsuite.console.page.federation;
 
+import static org.keycloak.testsuite.util.WaitUtils.waitAjaxForElement;
+import static org.keycloak.testsuite.util.WaitUtils.waitGuiForElement;
+
 import org.jboss.arquillian.graphene.findby.FindByJQuery;
 import org.keycloak.testsuite.console.page.fragment.OnOffSwitch;
 import org.keycloak.testsuite.page.Form;
@@ -8,10 +11,8 @@ import org.openqa.selenium.WebElement;
 import org.openqa.selenium.support.FindBy;
 import org.openqa.selenium.support.ui.Select;
 
-import static org.keycloak.testsuite.util.WaitUtils.waitGuiForElement;
-
 /**
- * Created by fkiss.
+ * @author fkiss, pdrozd
  */
 public class LdapUserProviderForm extends Form {
 
@@ -24,24 +25,33 @@ public class LdapUserProviderForm extends Form {
     @FindBy(id = "usernameLDAPAttribute")
     private WebElement usernameLDAPAttributeInput;
 
+    @FindBy(id = "rdnLDAPAttribute")
+    private WebElement rdnLDAPAttributeInput;
+
+    @FindBy(id = "uuidLDAPAttribute")
+    private WebElement uuidLDAPAttributeInput;
+
     @FindBy(id = "userObjectClasses")
     private WebElement userObjectClassesInput;
 
     @FindBy(id = "ldapConnectionUrl")
     private WebElement ldapConnectionUrlInput;
 
-    @FindBy(id = "ldapBaseDn")
-    private WebElement ldapBaseDnInput;
-
     @FindBy(id = "ldapUsersDn")
     private WebElement ldapUserDnInput;
 
+    @FindBy(id = "authType")
+    private Select authTypeSelect;
+
     @FindBy(id = "ldapBindDn")
     private WebElement ldapBindDnInput;
 
     @FindBy(id = "ldapBindCredential")
     private WebElement ldapBindCredentialInput;
 
+    @FindBy(id = "searchScope")
+    private Select searchScopeSelect;
+
     @FindBy(id = "kerberosRealm")
     private WebElement kerberosRealmInput;
 
@@ -72,59 +82,173 @@ public class LdapUserProviderForm extends Form {
     @FindByJQuery("a:contains('Test authentication')")
     private WebElement testAuthenticationButton;
 
-    @FindByJQuery("div[class='onoffswitch']:eq(0)")
+    @FindByJQuery("a:contains('Synchronize changed users')")
+    private WebElement synchronizeChangedUsersButton;
+
+    @FindByJQuery("button:contains('Synchronize all users')")
+    private WebElement synchronizeAllUsersButton;
+
+    @FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='syncRegistrations']]")
     private OnOffSwitch syncRegistrations;
 
-    @FindByJQuery("div[class='onoffswitch']:eq(1)")
+    @FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='connectionPooling']]")
     private OnOffSwitch connectionPooling;
 
-    @FindByJQuery("div[class='onoffswitch']:eq(2)")
+    @FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='pagination']]")
     private OnOffSwitch pagination;
 
-    @FindByJQuery("div[class='onoffswitch']:eq(3)")
+    @FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='userAccountControlsAfterPasswordUpdate']]")
+    private OnOffSwitch enableAccountAfterPasswordUpdate;
+
+    @FindBy(xpath = "//div[contains(@class,'onoffswitch') and ./input[@id='allowKerberosAuthentication']]")
     private OnOffSwitch allowKerberosAuth;
 
-    @FindByJQuery("div[class='onoffswitch']:eq(4)")
+    @FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='debug']]")
     private OnOffSwitch debug;
 
-    @FindByJQuery("div[class='onoffswitch']:eq(5)")
+    @FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='useKerberosForPasswordAuthentication']]")
     private OnOffSwitch useKerberosForPwdAuth;
 
-    @FindByJQuery("div[class='onoffswitch']:eq(6)")
+    @FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='compositeSwitch']]")
     private OnOffSwitch periodicFullSync;
 
-    @FindByJQuery("div[class='onoffswitch']:eq(7)")
+    @FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='changedSyncEnabled']]")
     private OnOffSwitch periodicChangedUsersSync;
 
-    @FindByJQuery("button:contains('Save')")
-    private WebElement saveButton;
+    public void setConsoleDisplayNameInput(String name) {
+        setInputValue(consoleDisplayNameInput, name);
+    }
+
+    public void setPriorityInput(Integer priority) {
+        setInputValue(priorityInput, String.valueOf(priority));
+    }
+
+    public void setUsernameLDAPAttributeInput(String usernameLDAPAttribute) {
+        setInputValue(usernameLDAPAttributeInput, usernameLDAPAttribute);
+    }
+
+    public void setRdnLDAPAttributeInput(String rdnLDAPAttribute) {
+        setInputValue(rdnLDAPAttributeInput, rdnLDAPAttribute);
+    }
+
+    public void setUuidLDAPAttributeInput(String uuidLDAPAttribute) {
+        setInputValue(uuidLDAPAttributeInput, uuidLDAPAttribute);
+    }
+
+    public void setUserObjectClassesInput(String userObjectClasses) {
+        setInputValue(userObjectClassesInput, userObjectClasses);
+    }
+
+    public void setLdapConnectionUrlInput(String ldapConnectionUrl) {
+        setInputValue(ldapConnectionUrlInput, ldapConnectionUrl);
+    }
+
+    public void setLdapUserDnInput(String ldapUserDn) {
+        setInputValue(ldapUserDnInput, ldapUserDn);
+    }
+
+    public void setLdapBindDnInput(String ldapBindDn) {
+        setInputValue(ldapBindDnInput, ldapBindDn);
+    }
+
+    public void setLdapBindCredentialInput(String ldapBindCredential) {
+        setInputValue(ldapBindCredentialInput, ldapBindCredential);
+    }
+
+    public void setKerberosRealmInput(String kerberosRealm) {
+        waitAjaxForElement(kerberosRealmInput);
+        setInputValue(kerberosRealmInput, kerberosRealm);
+    }
+
+    public void setServerPrincipalInput(String serverPrincipal) {
+        waitAjaxForElement(serverPrincipalInput);
+        setInputValue(serverPrincipalInput, serverPrincipal);
+    }
+
+    public void setKeyTabInput(String keyTab) {
+        waitAjaxForElement(keyTabInput);
+        setInputValue(keyTabInput, keyTab);
+    }
+
+    public void setBatchSizeForSyncInput(String batchSizeForSync) {
+        setInputValue(batchSizeForSyncInput, batchSizeForSync);
+    }
 
-    public void selectEditMode(String mode){
+    public void selectEditMode(String mode) {
         waitGuiForElement(By.id("editMode"));
         editModeSelect.selectByVisibleText(mode);
     }
 
-    public void selectVendor(String vendor){
-        waitGuiForElement(By.id("editMode"));
+    public void selectVendor(String vendor) {
+        waitGuiForElement(By.id("vendor"));
         vendorSelect.selectByVisibleText(vendor);
     }
 
-    public void configureLdap(String displayName, String editMode, String vendor, String connectionUrl, String userDN, String ldapBindDn, String ldapBindCredential){
-        consoleDisplayNameInput.sendKeys(displayName);
-        editModeSelect.selectByVisibleText(editMode);
-        selectVendor(vendor);
-        ldapConnectionUrlInput.sendKeys(connectionUrl);
-        ldapUserDnInput.sendKeys(userDN);
-        ldapBindDnInput.sendKeys(ldapBindDn);
-        ldapBindCredentialInput.sendKeys(ldapBindCredential);
-        saveButton.click();
+    public void selectAuthenticationType(String authenticationType) {
+        waitGuiForElement(By.id("authType"));
+        authTypeSelect.selectByVisibleText(authenticationType);
+    }
+
+    public void selectSearchScope(String searchScope) {
+        waitGuiForElement(By.id("searchScope"));
+        searchScopeSelect.selectByVisibleText(searchScope);
+    }
+
+    public void setSyncRegistrationsEnabled(boolean syncRegistrationsEnabled) {
+        this.syncRegistrations.setOn(syncRegistrationsEnabled);
+    }
+
+    public void setConnectionPoolingEnabled(boolean connectionPoolingEnabled) {
+        this.connectionPooling.setOn(connectionPoolingEnabled);
     }
 
-    public void testConnection(){
+    public void setPaginationEnabled(boolean paginationEnabled) {
+        this.pagination.setOn(paginationEnabled);
+    }
+
+    public void setAccountAfterPasswordUpdateEnabled(boolean enabled) {
+        if ((!enableAccountAfterPasswordUpdate.isOn() && enabled)
+                || !enabled && enableAccountAfterPasswordUpdate.isOn()) {
+            driver.findElement(By
+                    .xpath("//div[contains(@class,'onoffswitch') and ./input[@id='userAccountControlsAfterPasswordUpdate']]"))
+                    .findElements(By.tagName("span")).get(0).click();
+        }
+    }
+
+    public void setAllowKerberosAuthEnabled(boolean enabled) {
+        if ((!allowKerberosAuth.isOn() && enabled) || !enabled && allowKerberosAuth.isOn()) {
+            driver.findElement(
+                    By.xpath("//div[contains(@class,'onoffswitch') and ./input[@id='allowKerberosAuthentication']]"))
+                    .findElements(By.tagName("span")).get(0).click();
+        }
+    }
+
+    public void setDebugEnabled(boolean debugEnabled) {
+        this.debug.setOn(debugEnabled);
+    }
+
+    public void setUseKerberosForPwdAuthEnabled(boolean useKerberosForPwdAuthEnabled) {
+        this.useKerberosForPwdAuth.setOn(useKerberosForPwdAuthEnabled);
+    }
+
+    public void setPeriodicFullSyncEnabled(boolean periodicFullSyncEnabled) {
+        this.periodicFullSync.setOn(periodicFullSyncEnabled);
+    }
+
+    public void setPeriodicChangedUsersSyncEnabled(boolean periodicChangedUsersSyncEnabled) {
+        this.periodicChangedUsersSync.setOn(periodicChangedUsersSyncEnabled);
+    }
+
+    public void testConnection() {
         testConnectionButton.click();
     }
 
-    public void testAuthentication(){
+    public void testAuthentication() {
         testAuthenticationButton.click();
     }
+
+    public void synchronizeAllUsers() {
+        waitAjaxForElement(synchronizeAllUsersButton);
+        synchronizeAllUsersButton.click();
+    }
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/fragment/OnOffSwitch.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/fragment/OnOffSwitch.java
index 6319c4c..118e058 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/fragment/OnOffSwitch.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/fragment/OnOffSwitch.java
@@ -36,6 +36,14 @@ public class OnOffSwitch {
     @ArquillianResource
     private Actions actions;
 
+    public OnOffSwitch() {
+    }
+
+    public OnOffSwitch(WebElement root, Actions actions) {
+        this.root = root;
+        this.actions = actions;
+    }
+
     public boolean isOn() {
         waitAjaxForElement(root);
         return root.findElement(By.tagName("input")).isSelected();
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/WaitUtils.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/WaitUtils.java
index ac6ed22..00aaed3 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/WaitUtils.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/WaitUtils.java
@@ -17,6 +17,7 @@
  */
 package org.keycloak.testsuite.util;
 
+import java.util.concurrent.TimeUnit;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import static org.jboss.arquillian.graphene.Graphene.waitAjax;
@@ -31,19 +32,35 @@ import org.openqa.selenium.WebElement;
  */
 public final class WaitUtils {
 
+    public static final String PAGELOAD_TIMEOUT_PROP = "pageload.timeout";
+    public static final String IMPLICIT_TIMEOUT_PROP = "implicit.timeout";
+    public static final String SCRIPT_TIMEOUT_PROP = "script.timeout";
+    public static final String POLLING_INTERVAL_PROP = "polling.interval";
+
+    public static final Integer PAGELOAD_TIMEOUT = Integer.parseInt(System.getProperty(PAGELOAD_TIMEOUT_PROP, "5000"));
+    public static final Integer IMPLICIT_TIMEOUT = Integer.parseInt(System.getProperty(IMPLICIT_TIMEOUT_PROP, "3000"));
+    public static final Integer SCRIPT_TIMEOUT = Integer.parseInt(System.getProperty(SCRIPT_TIMEOUT_PROP, "3000"));
+
+    public static final Integer POLLING_INTERVAL = Integer.parseInt(System.getProperty(POLLING_INTERVAL_PROP, "1000"));
+
     public static void waitAjaxForElement(WebElement element) {
-        waitAjax().until()
-                .element(element).is().present();
+        waitAjax().withTimeout(SCRIPT_TIMEOUT, TimeUnit.MILLISECONDS).pollingEvery(POLLING_INTERVAL, TimeUnit.MILLISECONDS)
+                .until().element(element).is().present();
     }
 
     public static void waitAjaxForElementNotPresent(WebElement element) {
-        waitAjax().until()
-                .element(element).is().not().present();
+        waitAjax().withTimeout(SCRIPT_TIMEOUT, TimeUnit.MILLISECONDS).pollingEvery(POLLING_INTERVAL, TimeUnit.MILLISECONDS)
+                .until().element(element).is().not().present();
+    }
+
+    public static void waitAjaxForElementNotVisible(WebElement element) {
+        waitAjax().withTimeout(SCRIPT_TIMEOUT, TimeUnit.MILLISECONDS).pollingEvery(POLLING_INTERVAL, TimeUnit.MILLISECONDS)
+                .until().element(element).is().not().visible();
     }
 
     public static void waitGuiForElement(By element, String message) {
-        waitGui().until(message)
-                .element(element).is().present();
+        waitGui().withTimeout(IMPLICIT_TIMEOUT, TimeUnit.MILLISECONDS).pollingEvery(POLLING_INTERVAL, TimeUnit.MILLISECONDS)
+                .until(message).element(element).is().present();
     }
 
     public static void waitGuiForElement(By element) {
@@ -55,11 +72,13 @@ public final class WaitUtils {
     }
 
     public static void waitGuiForElementPresent(WebElement element, String message) {
-        waitGui().until(message).element(element).is().present();
+        waitGui().withTimeout(IMPLICIT_TIMEOUT, TimeUnit.MILLISECONDS).pollingEvery(POLLING_INTERVAL, TimeUnit.MILLISECONDS)
+                .until(message).element(element).is().present();
     }
 
     public static void waitGuiForElementNotPresent(WebElement element) {
-        waitGui().until().element(element).is().not().present();
+        waitGui().withTimeout(IMPLICIT_TIMEOUT, TimeUnit.MILLISECONDS).pollingEvery(POLLING_INTERVAL, TimeUnit.MILLISECONDS)
+                .until().element(element).is().not().present();
     }
 
     public static void pause(long millis) {
@@ -69,5 +88,5 @@ public final class WaitUtils {
             Logger.getLogger(WaitUtils.class.getName()).log(Level.SEVERE, null, ex);
         }
     }
-    
+
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java
index 99c5d67..e553b3d 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java
@@ -32,6 +32,7 @@ import org.keycloak.testsuite.auth.page.account.Account;
 import org.keycloak.testsuite.auth.page.login.OIDCLogin;
 import org.keycloak.testsuite.auth.page.login.UpdatePassword;
 import org.keycloak.testsuite.util.Timer;
+import org.keycloak.testsuite.util.WaitUtils;
 
 /**
  *
@@ -86,6 +87,7 @@ public abstract class AbstractKeycloakTest {
         driverSettings();
 
         if (!suiteContext.isAdminPasswordUpdated()) {
+            log.debug("updating admin password");
             updateMasterAdminPassword();
             suiteContext.setAdminPasswordUpdated(true);
         }
@@ -115,9 +117,9 @@ public abstract class AbstractKeycloakTest {
     }
 
     protected void driverSettings() {
-        driver.manage().timeouts().pageLoadTimeout(5, TimeUnit.SECONDS);
-        driver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
-        driver.manage().timeouts().setScriptTimeout(3, TimeUnit.SECONDS);
+        driver.manage().timeouts().pageLoadTimeout(WaitUtils.PAGELOAD_TIMEOUT, TimeUnit.MILLISECONDS);
+        driver.manage().timeouts().implicitlyWait(WaitUtils.IMPLICIT_TIMEOUT, TimeUnit.MILLISECONDS);
+        driver.manage().timeouts().setScriptTimeout(WaitUtils.SCRIPT_TIMEOUT, TimeUnit.MILLISECONDS);
         driver.manage().window().maximize();
     }
 
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractCorsExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractCorsExampleAdapterTest.java
index 72db4bb..24933cb 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractCorsExampleAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractCorsExampleAdapterTest.java
@@ -10,7 +10,6 @@ import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
 import org.keycloak.testsuite.adapter.page.AngularCorsProductExample;
 import org.keycloak.testsuite.adapter.page.CorsDatabaseServiceExample;
-import org.keycloak.testsuite.arquillian.jira.Jira;
 import org.keycloak.testsuite.auth.page.account.Account;
 
 import java.io.File;
@@ -63,7 +62,6 @@ public abstract class AbstractCorsExampleAdapterTest extends AbstractExampleAdap
         driver.manage().deleteAllCookies();
     }
 
-    @Jira("KEYCLOAK-1546")
     @Test
     public void angularCorsProductTest() {
         angularCorsProductExample.navigateTo();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java
index d31d390..4d0eed9 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java
@@ -1,34 +1,71 @@
 package org.keycloak.testsuite.adapter.example;
 
-import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
 import org.jboss.arquillian.container.test.api.Deployment;
 import org.jboss.arquillian.graphene.page.Page;
 import org.jboss.shrinkwrap.api.spec.WebArchive;
-import org.junit.*;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
-import org.keycloak.testsuite.auth.page.account.Account;
-import static org.keycloak.testsuite.util.IOUtil.loadRealm;
-import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
+import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
 import org.keycloak.testsuite.adapter.page.CustomerPortalExample;
 import org.keycloak.testsuite.adapter.page.DatabaseServiceExample;
 import org.keycloak.testsuite.adapter.page.ProductPortalExample;
-import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.auth.page.account.Account;
+import org.keycloak.testsuite.auth.page.account.Applications;
+import org.keycloak.testsuite.auth.page.login.OAuthGrant;
+import org.keycloak.testsuite.console.page.clients.ClientSettings;
+import org.keycloak.testsuite.console.page.clients.Clients;
+import org.keycloak.testsuite.console.page.events.Config;
+import org.keycloak.testsuite.console.page.events.LoginEvents;
 import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO;
+import static org.keycloak.testsuite.util.IOUtil.loadRealm;
+import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
 
 public abstract class AbstractDemoExampleAdapterTest extends AbstractExampleAdapterTest {
 
     @Page
-    private CustomerPortalExample customerPortalExample;
+    private CustomerPortalExample customerPortalExamplePage;
+
     @Page
-    private ProductPortalExample productPortalExample;
+    private ProductPortalExample productPortalExamplePage;
+
     @Page
-    private DatabaseServiceExample databaseServiceExample;
+    private DatabaseServiceExample databaseServiceExamplePage;
 
     @Page
-    private Account testRealmAccount;
+    private Account testRealmAccountPage;
+
+    @Page
+    private Clients clientsPage;
+
+    @Page
+    private ClientSettings clientSettingsPage;
+
+    @Page
+    private Config configPage;
+
+    @Page
+    private LoginEvents loginEventsPage;
+
+    @Page
+    private OAuthGrant oAuthGrantPage;
+
+    @Page
+    private Applications applicationsPage;
 
     @Deployment(name = CustomerPortalExample.DEPLOYMENT_NAME)
     private static WebArchive customerPortalExample() throws IOException {
@@ -56,27 +93,31 @@ public abstract class AbstractDemoExampleAdapterTest extends AbstractExampleAdap
         super.setDefaultPageUriParameters();
         testRealmPage.setAuthRealm(DEMO);
         testRealmLoginPage.setAuthRealm(DEMO);
-        testRealmAccount.setAuthRealm(DEMO);
+        testRealmAccountPage.setAuthRealm(DEMO);
+        clientsPage.setConsoleRealm(DEMO);
+        configPage.setConsoleRealm(DEMO);
+        loginEventsPage.setConsoleRealm(DEMO);
+        applicationsPage.setAuthRealm(DEMO);
     }
 
     @Before
     public void beforeDemoExampleTest() {
-        customerPortalExample.navigateTo();
+        customerPortalExamplePage.navigateTo();
         driver.manage().deleteAllCookies();
-        productPortalExample.navigateTo();
+        productPortalExamplePage.navigateTo();
         driver.manage().deleteAllCookies();
     }
 
     @Test
     public void customerPortalListingTest() {
 
-        customerPortalExample.navigateTo();
-        customerPortalExample.customerListing();
+        customerPortalExamplePage.navigateTo();
+        customerPortalExamplePage.customerListing();
 
         testRealmLoginPage.form().login("bburke@redhat.com", "password");
 
-        assertCurrentUrlStartsWith(customerPortalExample);
-        customerPortalExample.waitForCustomerListingHeader();
+        assertCurrentUrlStartsWith(customerPortalExamplePage);
+        customerPortalExamplePage.waitForCustomerListingHeader();
 
         Assert.assertTrue(driver.getPageSource().contains("Username: bburke@redhat.com"));
         Assert.assertTrue(driver.getPageSource().contains("Bill Burke"));
@@ -86,72 +127,140 @@ public abstract class AbstractDemoExampleAdapterTest extends AbstractExampleAdap
     @Test
     public void customerPortalSessionTest() {
 
-        customerPortalExample.navigateTo();
-        customerPortalExample.customerSession();
+        customerPortalExamplePage.navigateTo();
+        customerPortalExamplePage.customerSession();
 
         testRealmLoginPage.form().login("bburke@redhat.com", "password");
 
-        assertCurrentUrlStartsWith(customerPortalExample);
+        assertCurrentUrlStartsWith(customerPortalExamplePage);
 
-        customerPortalExample.waitForCustomerSessionHeader();
+        customerPortalExamplePage.waitForCustomerSessionHeader();
         Assert.assertTrue(driver.getPageSource().contains("You visited this page"));
     }
 
     @Test
     public void productPortalListingTest() {
 
-        productPortalExample.navigateTo();
-        productPortalExample.productListing();
+        productPortalExamplePage.navigateTo();
+        productPortalExamplePage.productListing();
 
         testRealmLoginPage.form().login("bburke@redhat.com", "password");
 
-        assertCurrentUrlStartsWith(productPortalExample);
-        productPortalExample.waitForProductListingHeader();
+        assertCurrentUrlStartsWith(productPortalExamplePage);
+        productPortalExamplePage.waitForProductListingHeader();
 
         Assert.assertTrue(driver.getPageSource().contains("iphone"));
         Assert.assertTrue(driver.getPageSource().contains("ipad"));
         Assert.assertTrue(driver.getPageSource().contains("ipod"));
 
-        productPortalExample.goToCustomers();
+        productPortalExamplePage.goToCustomers();
     }
 
     @Test
     public void goToProductPortalWithOneLoginTest() {
 
-        productPortalExample.navigateTo();
-        productPortalExample.productListing();
+        productPortalExamplePage.navigateTo();
+        productPortalExamplePage.productListing();
 
         testRealmLoginPage.form().login("bburke@redhat.com", "password");
 
-        assertCurrentUrlStartsWith(productPortalExample);
-        productPortalExample.waitForProductListingHeader();
-        productPortalExample.goToCustomers();
+        assertCurrentUrlStartsWith(productPortalExamplePage);
+        productPortalExamplePage.waitForProductListingHeader();
+        productPortalExamplePage.goToCustomers();
 
-        assertCurrentUrlStartsWith(customerPortalExample);
-        customerPortalExample.customerListing();
-        customerPortalExample.goToProducts();
-        assertCurrentUrlStartsWith(productPortalExample);
+        assertCurrentUrlStartsWith(customerPortalExamplePage);
+        customerPortalExamplePage.customerListing();
+        customerPortalExamplePage.goToProducts();
+        assertCurrentUrlStartsWith(productPortalExamplePage);
     }
 
     @Test
     public void logoutFromAllAppsTest() {
 
-        productPortalExample.navigateTo();
-        productPortalExample.productListing();
+        productPortalExamplePage.navigateTo();
+        productPortalExamplePage.productListing();
 
         testRealmLoginPage.form().login("bburke@redhat.com", "password");
 
-        assertCurrentUrlStartsWith(productPortalExample);
-        productPortalExample.waitForProductListingHeader();
+        assertCurrentUrlStartsWith(productPortalExamplePage);
+        productPortalExamplePage.waitForProductListingHeader();
+
+        if (isRelative()) { //KEYCLOAK-1546
+            productPortalExamplePage.logOut();
+        } else {
+            driver.navigate().to(testRealmPage.getOIDCLogoutUrl() + "?redirect_uri=" + productPortalExamplePage);
+        }
 
-        productPortalExample.logOut();
-        assertCurrentUrlStartsWith(productPortalExample);
-        productPortalExample.productListing();
+        assertCurrentUrlStartsWith(productPortalExamplePage);
+        productPortalExamplePage.productListing();
 
-        customerPortalExample.navigateTo();
-        customerPortalExample.customerListing();
+        customerPortalExamplePage.navigateTo();
+        customerPortalExamplePage.customerListing();
         testRealmLoginPage.form().login("bburke@redhat.com", "password");
 
-        customerPortalExample.logOut();
+        customerPortalExamplePage.logOut();
+    }
+
+    @Test
+    public void grantServerBasedApp() {
+        clientsPage.navigateTo();
+        loginPage.form().login(adminUser);
+
+        ClientResource clientResource = ApiUtil.findClientResourceByClientId(testRealmResource(), "customer-portal");
+        ClientRepresentation client = clientResource.toRepresentation();
+        client.setConsentRequired(true);
+        clientResource.update(client);
+
+        RealmRepresentation realm = testRealmResource().toRepresentation();
+        realm.setEventsEnabled(true);
+        realm.setEnabledEventTypes(Arrays.asList("REVOKE_GRANT", "LOGIN"));
+        testRealmResource().update(realm);
+
+        customerPortalExamplePage.navigateTo();
+        customerPortalExamplePage.customerSession();
+
+        loginPage.form().login("bburke@redhat.com", "password");
+
+        assertTrue(oAuthGrantPage.isCurrent());
+
+        oAuthGrantPage.accept();
+
+        assertTrue(driver.getPageSource().contains("Your hostname:"));
+        assertTrue(driver.getPageSource().contains("You visited this page"));
+
+        applicationsPage.navigateTo();
+        applicationsPage.revokeGrantForApplication("customer-portal");
+
+        customerPortalExamplePage.navigateTo();
+        customerPortalExamplePage.customerSession();
+
+        assertTrue(oAuthGrantPage.isCurrent());
+
+        loginEventsPage.navigateTo();
+        loginEventsPage.table().filter();
+        loginEventsPage.table().filterForm().addEventType("REVOKE_GRANT");
+        loginEventsPage.table().update();
+
+        List<WebElement> resultList = loginEventsPage.table().rows();
+
+        assertEquals(2, resultList.size());
+
+        resultList.get(0).findElement(By.xpath(".//td[text()='REVOKE_GRANT']"));
+        resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='account']"));
+        resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1']"));
+        resultList.get(0).findElement(By.xpath(".//td[text()='revoked_client']/../td[text()='customer-portal']"));
+
+        loginEventsPage.table().reset();
+        loginEventsPage.table().filterForm().addEventType("LOGIN");
+        loginEventsPage.table().update();
+        resultList = loginEventsPage.table().rows();
+
+        assertEquals(7, resultList.size());
+
+        resultList.get(0).findElement(By.xpath(".//td[text()='LOGIN']"));
+        resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='customer-portal']"));
+        resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1']"));
+        resultList.get(0).findElement(By.xpath(".//td[text()='username']/../td[text()='bburke@redhat.com']"));
+        resultList.get(0).findElement(By.xpath(".//td[text()='consent']/../td[text()='consent_granted']"));
     }
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractJSConsoleExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractJSConsoleExampleAdapterTest.java
index 61d8efa..ab63862 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractJSConsoleExampleAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractJSConsoleExampleAdapterTest.java
@@ -1,26 +1,59 @@
 package org.keycloak.testsuite.adapter.example;
 
-import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
 import org.jboss.arquillian.container.test.api.Deployment;
 import org.jboss.arquillian.graphene.page.Page;
 import org.jboss.shrinkwrap.api.spec.WebArchive;
-import static org.junit.Assert.assertTrue;
 import org.junit.Test;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
-import static org.keycloak.testsuite.util.IOUtil.loadRealm;
-import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
+import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
 import org.keycloak.testsuite.adapter.page.JSConsoleExample;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.auth.page.account.Applications;
+import org.keycloak.testsuite.auth.page.login.OAuthGrant;
+import org.keycloak.testsuite.console.page.clients.ClientSettings;
+import org.keycloak.testsuite.console.page.clients.Clients;
+import org.keycloak.testsuite.console.page.events.Config;
+import org.keycloak.testsuite.console.page.events.LoginEvents;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.keycloak.testsuite.auth.page.AuthRealm.EXAMPLE;
+import static org.keycloak.testsuite.util.IOUtil.loadRealm;
 import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlDoesntStartWith;
+import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
 import static org.keycloak.testsuite.util.WaitUtils.pause;
 
 public abstract class AbstractJSConsoleExampleAdapterTest extends AbstractExampleAdapterTest {
 
     @Page
-    private JSConsoleExample jsConsoleExample;
+    private JSConsoleExample jsConsoleExamplePage;
+
+    @Page
+    private Clients clientsPage;
+
+    @Page
+    private ClientSettings clientSettingsPage;
+
+    @Page
+    private Config configPage;
+
+    @Page
+    private LoginEvents loginEventsPage;
+
+    @Page
+    private OAuthGrant oAuthGrantPage;
+
+    @Page
+    private Applications applicationsPage;
 
     public static int TOKEN_LIFESPAN_LEEWAY = 3; // seconds
 
@@ -34,7 +67,7 @@ public abstract class AbstractJSConsoleExampleAdapterTest extends AbstractExampl
         RealmRepresentation jsConsoleRealm = loadRealm(new File(EXAMPLES_HOME_DIR + "/js-console/example-realm.json"));
 
         fixClientUrisUsingDeploymentUrl(jsConsoleRealm,
-                JSConsoleExample.CLIENT_ID, jsConsoleExample.buildUri().toASCIIString());
+                JSConsoleExample.CLIENT_ID, jsConsoleExamplePage.buildUri().toASCIIString());
 
         jsConsoleRealm.setAccessTokenLifespan(30 + TOKEN_LIFESPAN_LEEWAY); // seconds
 
@@ -49,85 +82,157 @@ public abstract class AbstractJSConsoleExampleAdapterTest extends AbstractExampl
 
     @Test
     public void testJSConsoleAuth() {
-        jsConsoleExample.navigateTo();
-        assertCurrentUrlStartsWith(jsConsoleExample);
+        jsConsoleExamplePage.navigateTo();
+        assertCurrentUrlStartsWith(jsConsoleExamplePage);
 
         pause(1000);
 
-        jsConsoleExample.logIn();
+        jsConsoleExamplePage.logIn();
         testRealmLoginPage.form().login("user", "invalid-password");
-        assertCurrentUrlDoesntStartWith(jsConsoleExample);
+        assertCurrentUrlDoesntStartWith(jsConsoleExamplePage);
 
         testRealmLoginPage.form().login("invalid-user", "password");
-        assertCurrentUrlDoesntStartWith(jsConsoleExample);
+        assertCurrentUrlDoesntStartWith(jsConsoleExamplePage);
 
         testRealmLoginPage.form().login("user", "password");
-        assertCurrentUrlStartsWith(jsConsoleExample);
+        assertCurrentUrlStartsWith(jsConsoleExamplePage);
         assertTrue(driver.getPageSource().contains("Init Success (Authenticated)"));
         assertTrue(driver.getPageSource().contains("Auth Success"));
 
         pause(1000);
 
-        jsConsoleExample.logOut();
-        assertCurrentUrlStartsWith(jsConsoleExample);
+        jsConsoleExamplePage.logOut();
+        assertCurrentUrlStartsWith(jsConsoleExamplePage);
         assertTrue(driver.getPageSource().contains("Init Success (Not Authenticated)"));
     }
 
     @Test
     public void testRefreshToken() {
-        jsConsoleExample.navigateTo();
-        assertCurrentUrlStartsWith(jsConsoleExample);
+        jsConsoleExamplePage.navigateTo();
+        assertCurrentUrlStartsWith(jsConsoleExamplePage);
 
-        jsConsoleExample.refreshToken();
+        jsConsoleExamplePage.refreshToken();
         assertTrue(driver.getPageSource().contains("Failed to refresh token"));
 
-        jsConsoleExample.logIn();
+        jsConsoleExamplePage.logIn();
         testRealmLoginPage.form().login("user", "password");
-        assertCurrentUrlStartsWith(jsConsoleExample);
+        assertCurrentUrlStartsWith(jsConsoleExamplePage);
         assertTrue(driver.getPageSource().contains("Auth Success"));
 
-        jsConsoleExample.refreshToken();
+        jsConsoleExamplePage.refreshToken();
         assertTrue(driver.getPageSource().contains("Auth Refresh Success"));
     }
 
     @Test
     public void testRefreshTokenIfUnder30s() {
-        jsConsoleExample.navigateTo();
-        assertCurrentUrlStartsWith(jsConsoleExample);
+        jsConsoleExamplePage.navigateTo();
+        assertCurrentUrlStartsWith(jsConsoleExamplePage);
 
-        jsConsoleExample.refreshToken();
+        jsConsoleExamplePage.refreshToken();
         assertTrue(driver.getPageSource().contains("Failed to refresh token"));
 
-        jsConsoleExample.logIn();
+        jsConsoleExamplePage.logIn();
         testRealmLoginPage.form().login("user", "password");
-        assertCurrentUrlStartsWith(jsConsoleExample);
+        assertCurrentUrlStartsWith(jsConsoleExamplePage);
         assertTrue(driver.getPageSource().contains("Auth Success"));
 
-        jsConsoleExample.refreshTokenIfUnder30s();
+        jsConsoleExamplePage.refreshTokenIfUnder30s();
         assertTrue(driver.getPageSource().contains("Token not refreshed, valid for"));
 
         pause((TOKEN_LIFESPAN_LEEWAY + 2) * 1000);
 
-        jsConsoleExample.refreshTokenIfUnder30s();
+        jsConsoleExamplePage.refreshTokenIfUnder30s();
         assertTrue(driver.getPageSource().contains("Auth Refresh Success"));
     }
-    
-    @Test 
+
+    @Test
     public void testGetProfile() {
-        jsConsoleExample.navigateTo();
-        assertCurrentUrlStartsWith(jsConsoleExample);
-        
-        jsConsoleExample.getProfile();
+        jsConsoleExamplePage.navigateTo();
+        assertCurrentUrlStartsWith(jsConsoleExamplePage);
+
+        jsConsoleExamplePage.getProfile();
         assertTrue(driver.getPageSource().contains("Failed to load profile"));
-        
-        jsConsoleExample.logIn();
+
+        jsConsoleExamplePage.logIn();
         testRealmLoginPage.form().login("user", "password");
-        assertCurrentUrlStartsWith(jsConsoleExample);
+        assertCurrentUrlStartsWith(jsConsoleExamplePage);
         assertTrue(driver.getPageSource().contains("Auth Success"));
 
-        jsConsoleExample.getProfile();
+        jsConsoleExamplePage.getProfile();
         assertTrue(driver.getPageSource().contains("Failed to load profile"));
         assertTrue(driver.getPageSource().contains("\"username\": \"user\""));
     }
 
+    @Test
+    public void grantBrowserBasedApp() {
+        testRealmPage.setAuthRealm(EXAMPLE);
+        testRealmLoginPage.setAuthRealm(EXAMPLE);
+        clientsPage.setConsoleRealm(EXAMPLE);
+        configPage.setConsoleRealm(EXAMPLE);
+        loginEventsPage.setConsoleRealm(EXAMPLE);
+        applicationsPage.setAuthRealm(EXAMPLE);
+
+        jsConsoleExamplePage.navigateTo();
+        driver.manage().deleteAllCookies();
+
+        clientsPage.navigateTo();
+
+        loginPage.form().login("admin", "admin");
+
+        ClientResource clientResource = ApiUtil.findClientResourceByClientId(testRealmResource(), "js-console");
+        ClientRepresentation client = clientResource.toRepresentation();
+        client.setConsentRequired(true);
+        clientResource.update(client);
+
+        RealmRepresentation realm = testRealmResource().toRepresentation();
+        realm.setEventsEnabled(true);
+        realm.setEnabledEventTypes(Arrays.asList("REVOKE_GRANT", "LOGIN"));
+        testRealmResource().update(realm);
+
+        jsConsoleExamplePage.navigateTo();
+        jsConsoleExamplePage.logIn();
+
+        testRealmLoginPage.form().login("user", "password");
+
+        assertTrue(oAuthGrantPage.isCurrent());
+        oAuthGrantPage.accept();
+
+        assertTrue(driver.getPageSource().contains("Init Success (Authenticated)"));
+
+        applicationsPage.navigateTo();
+        applicationsPage.revokeGrantForApplication("js-console");
+
+        jsConsoleExamplePage.navigateTo();
+        jsConsoleExamplePage.logIn();
+
+        assertTrue(oAuthGrantPage.isCurrent());
+
+        loginEventsPage.navigateTo();
+        loginEventsPage.table().filter();
+        loginEventsPage.table().filterForm().addEventType("REVOKE_GRANT");
+        loginEventsPage.table().update();
+
+        List<WebElement> resultList = loginEventsPage.table().rows();
+
+        assertEquals(2, resultList.size());
+
+        resultList.get(0).findElement(By.xpath(".//td[text()='REVOKE_GRANT']"));
+        resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='account']"));
+        resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1']"));
+        resultList.get(0).findElement(By.xpath(".//td[text()='revoked_client']/../td[text()='js-console']"));
+
+        loginEventsPage.table().reset();
+        loginEventsPage.table().filterForm().addEventType("LOGIN");
+        loginEventsPage.table().update();
+        resultList = loginEventsPage.table().rows();
+
+        assertEquals(7, resultList.size());
+
+        resultList.get(0).findElement(By.xpath(".//td[text()='LOGIN']"));
+        resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='js-console']"));
+        resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1']"));
+        resultList.get(0).findElement(By.xpath(".//td[text()='username']/../td[text()='user']"));
+        resultList.get(0).findElement(By.xpath(".//td[text()='consent']/../td[text()='consent_granted']"));
+    }
+
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java
index 9fb8165..8a470ad 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java
@@ -34,7 +34,6 @@ import org.keycloak.testsuite.adapter.page.CustomerPortal;
 import org.keycloak.testsuite.adapter.page.InputPortal;
 import org.keycloak.testsuite.adapter.page.ProductPortal;
 import org.keycloak.testsuite.adapter.page.SecurePortal;
-import org.keycloak.testsuite.arquillian.jira.Jira;
 import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlEquals;
 import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWithLoginUrlOf;
 import org.keycloak.util.BasicAuthHelper;
@@ -224,7 +223,6 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
     }
 
     @Test
-    @Jira(value = "KEYCLOAK-1478") // rejected
     public void testLoginSSOIdleRemoveExpiredUserSessions() {
         // test login to customer-portal which does a bearer request to customer-db
         customerPortal.navigateTo();
@@ -279,7 +277,6 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
         testRealmResource().update(demoRealmRep);
     }
 
-    @Jira("KEYCLOAK-518")
     @Test
     public void testNullBearerToken() {
         Client client = ClientBuilder.newClient();
@@ -293,7 +290,6 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
         client.close();
     }
 
-    @Jira("KEYCLOAK-1368")
     @Test
     public void testNullBearerTokenCustomErrorPage() {
         Client client = ClientBuilder.newClient();
@@ -326,7 +322,6 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
         client.close();
     }
 
-    @Jira("KEYCLOAK-518")
     @Test
     public void testBadUser() {
         Client client = ClientBuilder.newClient();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSessionServletAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSessionServletAdapterTest.java
index 79c0bef..cd969a3 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSessionServletAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSessionServletAdapterTest.java
@@ -16,7 +16,6 @@ import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
 import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.testsuite.adapter.page.SessionPortal;
-import org.keycloak.testsuite.arquillian.jira.Jira;
 import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO;
 import org.keycloak.testsuite.auth.page.account.Sessions;
 import org.keycloak.testsuite.auth.page.login.Login;
@@ -60,7 +59,6 @@ public abstract class AbstractSessionServletAdapterTest extends AbstractServlets
     @SecondBrowser
     protected WebDriver driver2;
 
-    @Jira("KEYCLOAK-732")
     @Test
     public void testSingleSessionInvalidated() {
 
@@ -102,7 +100,6 @@ public abstract class AbstractSessionServletAdapterTest extends AbstractServlets
     }
 
     @Test
-    @Jira("KEYCLOAK-741, KEYCLOAK-1485")
     public void testSessionInvalidatedAfterFailedRefresh() {
         RealmRepresentation testRealmRep = testRealmResource().toRepresentation();
         ClientResource sessionPortalRes = null;
@@ -139,7 +136,6 @@ public abstract class AbstractSessionServletAdapterTest extends AbstractServlets
     }
 
     @Test
-    @Jira("KEYCLOAK-942")
     public void testAdminApplicationLogout() {
         // login as bburke
         loginAndCheckSession(driver, testRealmLoginPage);
@@ -157,7 +153,6 @@ public abstract class AbstractSessionServletAdapterTest extends AbstractServlets
     }
 
     @Test
-    @Jira("KEYCLOAK-1216, KEYCLOAK-1485")
     public void testAccountManagementSessionsLogout() {
         // login as bburke
         loginAndCheckSession(driver, testRealmLoginPage);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java
new file mode 100644
index 0000000..6278653
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java
@@ -0,0 +1,110 @@
+package org.keycloak.testsuite.client;
+
+import org.junit.After;
+import org.junit.Before;
+import org.keycloak.client.registration.Auth;
+import org.keycloak.client.registration.ClientRegistration;
+import org.keycloak.client.registration.ClientRegistrationException;
+import org.keycloak.models.AdminRoles;
+import org.keycloak.models.Constants;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.AbstractKeycloakTest;
+
+import javax.ws.rs.NotFoundException;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public abstract class AbstractClientRegistrationTest extends AbstractKeycloakTest {
+
+    static final String REALM_NAME = "test";
+
+    ClientRegistration reg;
+
+    @Before
+    public void before() throws Exception {
+        reg = ClientRegistration.create().url(testContext.getAuthServerContextRoot() + "/auth", "test").build();
+    }
+
+    @After
+    public void after() throws Exception {
+        reg.close();
+    }
+
+    @Override
+    public void addTestRealms(List<RealmRepresentation> testRealms) {
+        RealmRepresentation rep = new RealmRepresentation();
+        rep.setEnabled(true);
+        rep.setRealm(REALM_NAME);
+        rep.setUsers(new LinkedList<UserRepresentation>());
+
+        LinkedList<CredentialRepresentation> credentials = new LinkedList<>();
+        CredentialRepresentation password = new CredentialRepresentation();
+        password.setType(CredentialRepresentation.PASSWORD);
+        password.setValue("password");
+        credentials.add(password);
+
+        UserRepresentation user = new UserRepresentation();
+        user.setEnabled(true);
+        user.setUsername("manage-clients");
+        user.setCredentials(credentials);
+        user.setClientRoles(Collections.singletonMap(Constants.REALM_MANAGEMENT_CLIENT_ID, Collections.singletonList(AdminRoles.MANAGE_CLIENTS)));
+
+        rep.getUsers().add(user);
+
+        UserRepresentation user2 = new UserRepresentation();
+        user2.setEnabled(true);
+        user2.setUsername("create-clients");
+        user2.setCredentials(credentials);
+        user2.setClientRoles(Collections.singletonMap(Constants.REALM_MANAGEMENT_CLIENT_ID, Collections.singletonList(AdminRoles.CREATE_CLIENT)));
+
+        rep.getUsers().add(user2);
+
+        UserRepresentation user3 = new UserRepresentation();
+        user3.setEnabled(true);
+        user3.setUsername("no-access");
+        user3.setCredentials(credentials);
+
+        rep.getUsers().add(user3);
+
+        testRealms.add(rep);
+    }
+
+    public ClientRepresentation createClient(ClientRepresentation client) throws ClientRegistrationException {
+        authManageClients();
+        ClientRepresentation response = reg.create(client);
+        reg.auth(null);
+        return response;
+    }
+
+    public ClientRepresentation getClient(String clientId) {
+        try {
+            return adminClient.realm(REALM_NAME).clients().get(clientId).toRepresentation();
+        } catch (NotFoundException e) {
+            return null;
+        }
+    }
+
+    void authCreateClients() {
+        reg.auth(Auth.token(getToken("create-clients", "password")));
+    }
+
+    void authManageClients() {
+        reg.auth(Auth.token(getToken("manage-clients", "password")));
+    }
+
+    void authNoAccess() {
+        reg.auth(Auth.token(getToken("no-access", "password")));
+    }
+
+    private String getToken(String username, String password) {
+        return oauthClient.getToken(REALM_NAME, "security-admin-console", null, username, password).getToken();
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AdapterInstallationConfigTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AdapterInstallationConfigTest.java
new file mode 100644
index 0000000..4e0712e
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AdapterInstallationConfigTest.java
@@ -0,0 +1,124 @@
+package org.keycloak.testsuite.client;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.client.registration.Auth;
+import org.keycloak.client.registration.ClientRegistrationException;
+import org.keycloak.client.registration.HttpErrorException;
+import org.keycloak.common.enums.SslRequired;
+import org.keycloak.representations.adapters.config.AdapterConfig;
+import org.keycloak.representations.idm.ClientRepresentation;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class AdapterInstallationConfigTest extends AbstractClientRegistrationTest {
+
+    private ClientRepresentation client;
+    private ClientRepresentation client2;
+    private ClientRepresentation clientPublic;
+    private String publicKey;
+
+    @Before
+    public void before() throws Exception {
+        super.before();
+
+        publicKey = adminClient.realm(REALM_NAME).toRepresentation().getPublicKey();
+
+        client = new ClientRepresentation();
+        client.setEnabled(true);
+        client.setClientId("RegistrationAccessTokenTest");
+        client.setSecret("RegistrationAccessTokenTestClientSecret");
+        client.setPublicClient(false);
+        client.setRegistrationAccessToken("RegistrationAccessTokenTestRegistrationAccessToken");
+        client.setRootUrl("http://root");
+        client = createClient(client);
+        client.setSecret("RegistrationAccessTokenTestClientSecret");
+
+        client2 = new ClientRepresentation();
+        client2.setEnabled(true);
+        client2.setClientId("RegistrationAccessTokenTest2");
+        client2.setSecret("RegistrationAccessTokenTestClientSecret");
+        client2.setPublicClient(false);
+        client2.setRegistrationAccessToken("RegistrationAccessTokenTestRegistrationAccessToken");
+        client2.setRootUrl("http://root");
+        client2 = createClient(client2);
+
+        clientPublic = new ClientRepresentation();
+        clientPublic.setEnabled(true);
+        clientPublic.setClientId("RegistrationAccessTokenTestPublic");
+        clientPublic.setPublicClient(true);
+        clientPublic.setRegistrationAccessToken("RegistrationAccessTokenTestRegistrationAccessTokenPublic");
+        clientPublic.setRootUrl("http://root");
+        clientPublic = createClient(clientPublic);
+    }
+
+    @Test
+    public void getConfigWithRegistrationAccessToken() throws ClientRegistrationException {
+        reg.auth(Auth.token(client.getRegistrationAccessToken()));
+
+        AdapterConfig config = reg.getAdapterConfig(client.getClientId());
+        assertNotNull(config);
+    }
+
+    @Test
+    public void getConfig() throws ClientRegistrationException {
+        reg.auth(Auth.client(client.getClientId(), client.getSecret()));
+
+        AdapterConfig config = reg.getAdapterConfig(client.getClientId());
+        assertNotNull(config);
+
+        assertEquals(testContext.getAuthServerContextRoot() + "/auth", config.getAuthServerUrl());
+        assertEquals("test", config.getRealm());
+
+        assertEquals(1, config.getCredentials().size());
+        assertEquals(client.getSecret(), config.getCredentials().get("secret"));
+
+        assertEquals(publicKey, config.getRealmKey());
+        assertEquals(client.getClientId(), config.getResource());
+        assertEquals(SslRequired.EXTERNAL.name().toLowerCase(), config.getSslRequired());
+    }
+
+    @Test
+    public void getConfigMissingSecret() throws ClientRegistrationException {
+        reg.auth(null);
+
+        try {
+            reg.getAdapterConfig(client.getClientId());
+            fail("Expected 403");
+        } catch (ClientRegistrationException e) {
+            assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+        }
+    }
+
+    @Test
+    public void getConfigWrongClient() throws ClientRegistrationException {
+        reg.auth(Auth.client(client.getClientId(), client.getSecret()));
+
+        try {
+            reg.getAdapterConfig(client2.getClientId());
+            fail("Expected 403");
+        } catch (ClientRegistrationException e) {
+            assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+        }
+    }
+
+    @Test
+    public void getConfigPublicClient() throws ClientRegistrationException {
+        reg.auth(null);
+
+        AdapterConfig config = reg.getAdapterConfig(clientPublic.getClientId());
+        assertNotNull(config);
+
+        assertEquals("test", config.getRealm());
+
+        assertEquals(0, config.getCredentials().size());
+
+        assertEquals(publicKey, config.getRealmKey());
+        assertEquals(clientPublic.getClientId(), config.getResource());
+        assertEquals(SslRequired.EXTERNAL.name().toLowerCase(), config.getSslRequired());
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTest.java
index 1d4b011..3390988 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTest.java
@@ -1,306 +1,199 @@
 package org.keycloak.testsuite.client;
 
-import org.junit.After;
-import org.junit.Before;
 import org.junit.Test;
-import org.keycloak.client.registration.ClientRegistration;
+import org.keycloak.client.registration.Auth;
 import org.keycloak.client.registration.ClientRegistrationException;
 import org.keycloak.client.registration.HttpErrorException;
-import org.keycloak.models.AdminRoles;
-import org.keycloak.models.Constants;
-import org.keycloak.representations.AccessTokenResponse;
 import org.keycloak.representations.idm.ClientRepresentation;
-import org.keycloak.representations.idm.CredentialRepresentation;
-import org.keycloak.representations.idm.RealmRepresentation;
-import org.keycloak.representations.idm.UserRepresentation;
-import org.keycloak.testsuite.AbstractKeycloakTest;
 
+import javax.ws.rs.NotFoundException;
 import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
 
 import static org.junit.Assert.*;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class ClientRegistrationTest extends AbstractKeycloakTest {
+public class ClientRegistrationTest extends AbstractClientRegistrationTest {
 
-    private static final String REALM_NAME = "test";
     private static final String CLIENT_ID = "test-client";
     private static final String CLIENT_SECRET = "test-client-secret";
 
-    private ClientRegistration clientRegistrationAsAdmin;
-    private ClientRegistration clientRegistrationAsClient;
-
-    @Before
-    public void before() throws ClientRegistrationException {
-        clientRegistrationAsAdmin = clientBuilder().auth(getToken("manage-clients", "password")).build();
-        clientRegistrationAsClient = clientBuilder().auth(CLIENT_ID, CLIENT_SECRET).build();
-    }
-
-    @After
-    public void after() throws ClientRegistrationException {
-        clientRegistrationAsAdmin.close();
-        clientRegistrationAsClient.close();
-    }
-
-    @Override
-    public void addTestRealms(List<RealmRepresentation> testRealms) {
-        RealmRepresentation rep = new RealmRepresentation();
-        rep.setEnabled(true);
-        rep.setRealm(REALM_NAME);
-        rep.setUsers(new LinkedList<UserRepresentation>());
-
-        LinkedList<CredentialRepresentation> credentials = new LinkedList<>();
-        CredentialRepresentation password = new CredentialRepresentation();
-        password.setType(CredentialRepresentation.PASSWORD);
-        password.setValue("password");
-        credentials.add(password);
-
-        UserRepresentation user = new UserRepresentation();
-        user.setEnabled(true);
-        user.setUsername("manage-clients");
-        user.setCredentials(credentials);
-        user.setClientRoles(Collections.singletonMap(Constants.REALM_MANAGEMENT_CLIENT_ID, Collections.singletonList(AdminRoles.MANAGE_CLIENTS)));
-
-        rep.getUsers().add(user);
-
-        UserRepresentation user2 = new UserRepresentation();
-        user2.setEnabled(true);
-        user2.setUsername("create-clients");
-        user2.setCredentials(credentials);
-        user2.setClientRoles(Collections.singletonMap(Constants.REALM_MANAGEMENT_CLIENT_ID, Collections.singletonList(AdminRoles.CREATE_CLIENT)));
-
-        rep.getUsers().add(user2);
-
-        UserRepresentation user3 = new UserRepresentation();
-        user3.setEnabled(true);
-        user3.setUsername("no-access");
-        user3.setCredentials(credentials);
-
-        rep.getUsers().add(user3);
-
-        testRealms.add(rep);
-    }
-
-    private void registerClient(ClientRegistration clientRegistration) throws ClientRegistrationException {
+    private ClientRepresentation registerClient() throws ClientRegistrationException {
         ClientRepresentation client = new ClientRepresentation();
         client.setClientId(CLIENT_ID);
         client.setSecret(CLIENT_SECRET);
 
-        ClientRepresentation createdClient = clientRegistration.create(client);
+        ClientRepresentation createdClient = reg.create(client);
         assertEquals(CLIENT_ID, createdClient.getClientId());
 
         client = adminClient.realm(REALM_NAME).clients().get(createdClient.getId()).toRepresentation();
         assertEquals(CLIENT_ID, client.getClientId());
 
-        AccessTokenResponse token2 = oauthClient.getToken(REALM_NAME, CLIENT_ID, CLIENT_SECRET, "manage-clients", "password");
-        assertNotNull(token2.getToken());
+        return client;
     }
 
     @Test
     public void registerClientAsAdmin() throws ClientRegistrationException {
-        registerClient(clientRegistrationAsAdmin);
+        authManageClients();
+        registerClient();
     }
 
     @Test
     public void registerClientAsAdminWithCreateOnly() throws ClientRegistrationException {
-        ClientRegistration clientRegistration = clientBuilder().auth(getToken("create-clients", "password")).build();
-        try {
-            registerClient(clientRegistration);
-        } finally {
-            clientRegistration.close();
-        }
+        authCreateClients();
+        registerClient();
     }
 
     @Test
     public void registerClientAsAdminWithNoAccess() throws ClientRegistrationException {
-        ClientRegistration clientRegistration = clientBuilder().auth(getToken("no-access", "password")).build();
+        authNoAccess();
         try {
-            registerClient(clientRegistration);
+            registerClient();
             fail("Expected 403");
         } catch (ClientRegistrationException e) {
             assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
-        } finally {
-            clientRegistration.close();
         }
     }
 
     @Test
+    public void getClientAsAdmin() throws ClientRegistrationException {
+        registerClientAsAdmin();
+        ClientRepresentation rep = reg.get(CLIENT_ID);
+        assertNotNull(rep);
+    }
+
+    @Test
     public void getClientAsAdminWithCreateOnly() throws ClientRegistrationException {
-        registerClient(clientRegistrationAsAdmin);
-        ClientRegistration clientRegistration = clientBuilder().auth(getToken("create-clients", "password")).build();
+        registerClientAsAdmin();
+        authCreateClients();
         try {
-            clientRegistration.get(CLIENT_ID);
+            reg.get(CLIENT_ID);
             fail("Expected 403");
         } catch (ClientRegistrationException e) {
             assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
-        } finally {
-            clientRegistration.close();
         }
     }
 
     @Test
-    public void wrongClient() throws ClientRegistrationException {
-        registerClient(clientRegistrationAsAdmin);
-
-        ClientRepresentation client = new ClientRepresentation();
-        client.setClientId("test-client-2");
-        client.setSecret("test-client-2-secret");
-
-        clientRegistrationAsAdmin.create(client);
-
-        ClientRegistration clientRegistration = clientBuilder().auth("test-client-2", "test-client-2-secret").build();
-
-        client = clientRegistration.get("test-client-2");
-        assertNotNull(client);
-        assertEquals("test-client-2", client.getClientId());
-
+    public void getClientAsAdminWithNoAccess() throws ClientRegistrationException {
+        registerClientAsAdmin();
+        authNoAccess();
         try {
-            try {
-                clientRegistration.get(CLIENT_ID);
-                fail("Expected 403");
-            } catch (ClientRegistrationException e) {
-                assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
-            }
-
-            client = clientRegistrationAsAdmin.get(CLIENT_ID);
-            try {
-                clientRegistration.update(client);
-                fail("Expected 403");
-            } catch (ClientRegistrationException e) {
-                assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
-            }
-
-            try {
-                clientRegistration.delete(CLIENT_ID);
-                fail("Expected 403");
-            } catch (ClientRegistrationException e) {
-                assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
-            }
-        }
-        finally {
-            clientRegistration.close();
+            reg.get(CLIENT_ID);
+            fail("Expected 403");
+        } catch (ClientRegistrationException e) {
+            assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
         }
     }
 
     @Test
-    public void getClientAsAdminWithNoAccess() throws ClientRegistrationException {
-        registerClient(clientRegistrationAsAdmin);
-        ClientRegistration clientRegistration = clientBuilder().auth(getToken("no-access", "password")).build();
+    public void getClientNotFound() throws ClientRegistrationException {
+        authManageClients();
         try {
-            clientRegistration.get(CLIENT_ID);
+            reg.get(CLIENT_ID);
             fail("Expected 403");
         } catch (ClientRegistrationException e) {
             assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
-        } finally {
-            clientRegistration.close();
         }
     }
 
-    private void updateClient(ClientRegistration clientRegistration) throws ClientRegistrationException {
-        ClientRepresentation client = clientRegistration.get(CLIENT_ID);
+    private void updateClient() throws ClientRegistrationException {
+        ClientRepresentation client = reg.get(CLIENT_ID);
         client.setRedirectUris(Collections.singletonList("http://localhost:8080/app"));
 
-        clientRegistration.update(client);
+        reg.update(client);
 
-        ClientRepresentation updatedClient = clientRegistration.get(CLIENT_ID);
+        ClientRepresentation updatedClient = reg.get(CLIENT_ID);
 
         assertEquals(1, updatedClient.getRedirectUris().size());
         assertEquals("http://localhost:8080/app", updatedClient.getRedirectUris().get(0));
     }
 
+
     @Test
     public void updateClientAsAdmin() throws ClientRegistrationException {
-        registerClient(clientRegistrationAsAdmin);
-        updateClient(clientRegistrationAsAdmin);
+        registerClientAsAdmin();
+
+        authManageClients();
+        updateClient();
     }
 
     @Test
     public void updateClientAsAdminWithCreateOnly() throws ClientRegistrationException {
-        ClientRegistration clientRegistration = clientBuilder().auth(getToken("create-clients", "password")).build();
+        authCreateClients();
         try {
-            updateClient(clientRegistration);
+            updateClient();
             fail("Expected 403");
         } catch (ClientRegistrationException e) {
             assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
-        } finally {
-            clientRegistration.close();
         }
     }
 
     @Test
     public void updateClientAsAdminWithNoAccess() throws ClientRegistrationException {
-        ClientRegistration clientRegistration = clientBuilder().auth(getToken("no-access", "password")).build();
+        authNoAccess();
         try {
-            updateClient(clientRegistration);
+            updateClient();
             fail("Expected 403");
         } catch (ClientRegistrationException e) {
             assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
-        } finally {
-            clientRegistration.close();
         }
     }
 
     @Test
-    public void updateClientAsClient() throws ClientRegistrationException {
-        registerClient(clientRegistrationAsAdmin);
-        updateClient(clientRegistrationAsClient);
+    public void updateClientNotFound() throws ClientRegistrationException {
+        authManageClients();
+        try {
+            updateClient();
+            fail("Expected 403");
+        } catch (ClientRegistrationException e) {
+            assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+        }
     }
 
-    private void deleteClient(ClientRegistration clientRegistration) throws ClientRegistrationException {
-        clientRegistration.delete(CLIENT_ID);
-
-        // Can't authenticate as client after client is deleted
-        ClientRepresentation client = clientRegistrationAsAdmin.get(CLIENT_ID);
-        assertNull(client);
+    private void deleteClient(ClientRepresentation client) throws ClientRegistrationException {
+        reg.delete(CLIENT_ID);
+        try {
+            adminClient.realm("test").clients().get(client.getId()).toRepresentation();
+            fail("Expected 403");
+        } catch (NotFoundException e) {
+        }
     }
 
     @Test
     public void deleteClientAsAdmin() throws ClientRegistrationException {
-        registerClient(clientRegistrationAsAdmin);
-        deleteClient(clientRegistrationAsAdmin);
+        authCreateClients();
+        ClientRepresentation client = registerClient();
+
+        authManageClients();
+        deleteClient(client);
     }
 
     @Test
     public void deleteClientAsAdminWithCreateOnly() throws ClientRegistrationException {
-        ClientRegistration clientRegistration = clientBuilder().auth(getToken("create-clients", "password")).build();
+        authManageClients();
+        ClientRepresentation client = registerClient();
         try {
-            deleteClient(clientRegistration);
+            authCreateClients();
+            deleteClient(client);
             fail("Expected 403");
         } catch (ClientRegistrationException e) {
             assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
-        } finally {
-            clientRegistration.close();
         }
     }
 
     @Test
     public void deleteClientAsAdminWithNoAccess() throws ClientRegistrationException {
-        ClientRegistration clientRegistration = clientBuilder().auth(getToken("no-access", "password")).build();
+        authManageClients();
+        ClientRepresentation client = registerClient();
         try {
-            deleteClient(clientRegistration);
+            authNoAccess();
+            deleteClient(client);
             fail("Expected 403");
         } catch (ClientRegistrationException e) {
             assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
-        } finally {
-            clientRegistration.close();
         }
     }
 
-    @Test
-    public void deleteClientAsClient() throws ClientRegistrationException {
-        registerClient(clientRegistrationAsAdmin);
-        deleteClient(clientRegistrationAsClient);
-    }
-
-    private ClientRegistration.ClientRegistrationBuilder clientBuilder() {
-        return ClientRegistration.create().realm("test").authServerUrl(testContext.getAuthServerContextRoot() + "/auth");
-    }
-
-    private String getToken(String username, String password) {
-        return oauthClient.getToken(REALM_NAME, "security-admin-console", null, username, password).getToken();
-    }
-
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/InitialAccessTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/InitialAccessTokenTest.java
new file mode 100644
index 0000000..6698b5e
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/InitialAccessTokenTest.java
@@ -0,0 +1,103 @@
+package org.keycloak.testsuite.client;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.ClientInitialAccessResource;
+import org.keycloak.client.registration.Auth;
+import org.keycloak.client.registration.ClientRegistrationException;
+import org.keycloak.client.registration.HttpErrorException;
+import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
+import org.keycloak.representations.idm.ClientInitialAccessPresentation;
+import org.keycloak.representations.idm.ClientRepresentation;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class InitialAccessTokenTest extends AbstractClientRegistrationTest {
+
+    private ClientInitialAccessResource resource;
+
+    @Before
+    public void before() throws Exception {
+        super.before();
+
+        resource = adminClient.realm(REALM_NAME).clientInitialAccess();
+    }
+
+    @Test
+    public void create() throws ClientRegistrationException, InterruptedException {
+        ClientInitialAccessPresentation response = resource.create(new ClientInitialAccessCreatePresentation());
+
+        reg.auth(Auth.token(response));
+
+        ClientRepresentation rep = new ClientRepresentation();
+
+        Thread.sleep(2);
+
+        ClientRepresentation created = reg.create(rep);
+        Assert.assertNotNull(created);
+
+        try {
+            reg.create(rep);
+        } catch (ClientRegistrationException e) {
+            Assert.assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+        }
+    }
+
+    @Test
+    public void createMultiple() throws ClientRegistrationException {
+        ClientInitialAccessPresentation response = resource.create(new ClientInitialAccessCreatePresentation(0, 2));
+
+        reg.auth(Auth.token(response));
+
+        ClientRepresentation rep = new ClientRepresentation();
+
+        ClientRepresentation created = reg.create(rep);
+        Assert.assertNotNull(created);
+
+        created = reg.create(rep);
+        Assert.assertNotNull(created);
+
+        try {
+            reg.create(rep);
+        } catch (ClientRegistrationException e) {
+            Assert.assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+        }
+    }
+
+    @Test
+    public void createExpired() throws ClientRegistrationException, InterruptedException {
+        ClientInitialAccessPresentation response = resource.create(new ClientInitialAccessCreatePresentation(1, 1));
+
+        reg.auth(Auth.token(response));
+
+        ClientRepresentation rep = new ClientRepresentation();
+
+        Thread.sleep(2);
+
+        try {
+            reg.create(rep);
+        } catch (ClientRegistrationException e) {
+            Assert.assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+        }
+    }
+
+    @Test
+    public void createDeleted() throws ClientRegistrationException, InterruptedException {
+        ClientInitialAccessPresentation response = resource.create(new ClientInitialAccessCreatePresentation());
+
+        reg.auth(Auth.token(response));
+
+        resource.delete(response.getId());
+
+        ClientRepresentation rep = new ClientRepresentation();
+
+        try {
+            reg.create(rep);
+        } catch (ClientRegistrationException e) {
+            Assert.assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+        }
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java
new file mode 100644
index 0000000..9696274
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java
@@ -0,0 +1,85 @@
+package org.keycloak.testsuite.client;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.client.registration.Auth;
+import org.keycloak.client.registration.ClientRegistrationException;
+import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
+import org.keycloak.representations.idm.ClientInitialAccessPresentation;
+import org.keycloak.representations.oidc.OIDCClientRepresentation;
+
+import java.util.Collections;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest {
+
+    @Before
+    public void before() throws Exception {
+        super.before();
+
+        ClientInitialAccessPresentation token = adminClient.realm(REALM_NAME).clientInitialAccess().create(new ClientInitialAccessCreatePresentation(0, 10));
+        reg.auth(Auth.token(token));
+    }
+
+    public OIDCClientRepresentation create() throws ClientRegistrationException {
+        OIDCClientRepresentation client = new OIDCClientRepresentation();
+        client.setClientName("RegistrationAccessTokenTest");
+        client.setClientUri("http://root");
+        client.setRedirectUris(Collections.singletonList("http://redirect"));
+
+        OIDCClientRepresentation response = reg.oidc().create(client);
+
+        return response;
+    }
+
+    @Test
+    public void createClient() throws ClientRegistrationException {
+        OIDCClientRepresentation response = create();
+
+        assertNotNull(response.getRegistrationAccessToken());
+        assertNotNull(response.getClientIdIssuedAt());
+        assertNotNull(response.getClientId());
+        assertNull(response.getClientSecretExpiresAt());
+        assertNotNull(response.getRegistrationClientUri());
+        assertEquals("RegistrationAccessTokenTest", response.getClientName());
+        assertEquals("http://root", response.getClientUri());
+        assertEquals(1, response.getRedirectUris().size());
+        assertEquals("http://redirect", response.getRedirectUris().get(0));
+    }
+
+    @Test
+    public void getClient() throws ClientRegistrationException {
+        OIDCClientRepresentation response = create();
+        reg.auth(Auth.token(response));
+
+        OIDCClientRepresentation rep = reg.oidc().get(response.getClientId());
+        assertNotNull(rep);
+        assertNotEquals(response.getRegistrationAccessToken(), rep.getRegistrationAccessToken());
+    }
+
+    @Test
+    public void updateClient() throws ClientRegistrationException {
+        OIDCClientRepresentation response = create();
+        reg.auth(Auth.token(response));
+
+        response.setRedirectUris(Collections.singletonList("http://newredirect"));
+
+        OIDCClientRepresentation updated = reg.oidc().update(response);
+
+        assertEquals(1, updated.getRedirectUris().size());
+        assertEquals("http://newredirect", updated.getRedirectUris().get(0));
+    }
+
+    @Test
+    public void deleteClient() throws ClientRegistrationException {
+        OIDCClientRepresentation response = create();
+        reg.auth(Auth.token(response));
+
+        reg.oidc().delete(response);
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/RegistrationAccessTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/RegistrationAccessTokenTest.java
new file mode 100644
index 0000000..cad28ab
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/RegistrationAccessTokenTest.java
@@ -0,0 +1,121 @@
+package org.keycloak.testsuite.client;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.client.registration.Auth;
+import org.keycloak.client.registration.ClientRegistrationException;
+import org.keycloak.client.registration.HttpErrorException;
+import org.keycloak.representations.idm.ClientRepresentation;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RegistrationAccessTokenTest extends AbstractClientRegistrationTest {
+
+    private ClientRepresentation client;
+
+    @Before
+    public void before() throws Exception {
+        super.before();
+
+        ClientRepresentation c = new ClientRepresentation();
+        c.setEnabled(true);
+        c.setClientId("RegistrationAccessTokenTest");
+        c.setSecret("RegistrationAccessTokenTestClientSecret");
+        c.setRootUrl("http://root");
+
+        client = createClient(c);
+
+        reg.auth(Auth.token(client.getRegistrationAccessToken()));
+    }
+
+    private ClientRepresentation assertRead(String id, String registrationAccess, boolean expectSuccess) throws ClientRegistrationException {
+        if (expectSuccess) {
+            reg.auth(Auth.token(registrationAccess));
+            ClientRepresentation rep = reg.get(id);
+            assertNotNull(rep);
+            return rep;
+        } else {
+            reg.auth(Auth.token(registrationAccess));
+            try {
+                reg.get(client.getClientId());
+                fail("Expected 403");
+            } catch (ClientRegistrationException e) {
+                assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+            }
+        }
+        return null;
+    }
+
+    @Test
+    public void getClientWithRegistrationToken() throws ClientRegistrationException {
+        ClientRepresentation rep = reg.get(client.getClientId());
+        assertNotNull(rep);
+        assertNotEquals(client.getRegistrationAccessToken(), rep.getRegistrationAccessToken());
+
+        // check registration access token is updated
+        assertRead(client.getClientId(), client.getRegistrationAccessToken(), false);
+        assertRead(client.getClientId(), rep.getRegistrationAccessToken(), true);
+    }
+
+    @Test
+    public void getClientWithBadRegistrationToken() throws ClientRegistrationException {
+        reg.auth(Auth.token("invalid"));
+        try {
+            reg.get(client.getClientId());
+            fail("Expected 403");
+        } catch (ClientRegistrationException e) {
+            assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+        }
+    }
+
+    @Test
+    public void updateClientWithRegistrationToken() throws ClientRegistrationException {
+        client.setRootUrl("http://newroot");
+
+        ClientRepresentation rep = reg.update(client);
+
+        assertEquals("http://newroot", getClient(client.getId()).getRootUrl());
+        assertNotEquals(client.getRegistrationAccessToken(), rep.getRegistrationAccessToken());
+
+        // check registration access token is updated
+        assertRead(client.getClientId(), client.getRegistrationAccessToken(), false);
+        assertRead(client.getClientId(), rep.getRegistrationAccessToken(), true);
+    }
+
+    @Test
+    public void updateClientWithBadRegistrationToken() throws ClientRegistrationException {
+        client.setRootUrl("http://newroot");
+
+        reg.auth(Auth.token("invalid"));
+        try {
+            reg.update(client);
+            fail("Expected 403");
+        } catch (ClientRegistrationException e) {
+            assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+        }
+
+        assertEquals("http://root", getClient(client.getId()).getRootUrl());
+    }
+
+    @Test
+    public void deleteClientWithRegistrationToken() throws ClientRegistrationException {
+        reg.delete(client);
+        assertNull(getClient(client.getId()));
+    }
+
+    @Test
+    public void deleteClientWithBadRegistrationToken() throws ClientRegistrationException {
+        reg.auth(Auth.token("invalid"));
+        try {
+            reg.delete(client);
+            fail("Expected 403");
+        } catch (ClientRegistrationException e) {
+            assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+        }
+        assertNotNull(getClient(client.getId()));
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/SAMLClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/SAMLClientRegistrationTest.java
new file mode 100644
index 0000000..1f5eab8
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/SAMLClientRegistrationTest.java
@@ -0,0 +1,42 @@
+package org.keycloak.testsuite.client;
+
+import org.apache.commons.io.IOUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.client.registration.Auth;
+import org.keycloak.client.registration.ClientRegistrationException;
+import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
+import org.keycloak.representations.idm.ClientInitialAccessPresentation;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.oidc.OIDCClientRepresentation;
+
+import java.io.IOException;
+import java.util.Collections;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class SAMLClientRegistrationTest extends AbstractClientRegistrationTest {
+
+    @Before
+    public void before() throws Exception {
+        super.before();
+
+        ClientInitialAccessPresentation token = adminClient.realm(REALM_NAME).clientInitialAccess().create(new ClientInitialAccessCreatePresentation(0, 10));
+        reg.auth(Auth.token(token));
+    }
+
+    @Test
+    public void createClient() throws ClientRegistrationException, IOException {
+        String entityDescriptor = IOUtils.toString(getClass().getResourceAsStream("/clientreg-test/saml-entity-descriptor.xml"));
+        ClientRepresentation response = reg.saml().create(entityDescriptor);
+
+        assertNotNull(response.getRegistrationAccessToken());
+        assertEquals("loadbalancer-9.siroe.com", response.getClientId());
+        assertEquals(1, response.getRedirectUris().size());
+        assertEquals("https://LoadBalancer-9.siroe.com:3443/federation/Consumer/metaAlias/sp", response.getRedirectUris().get(0));
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/authentication/BindingsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/authentication/BindingsTest.java
new file mode 100644
index 0000000..a89eac6
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/authentication/BindingsTest.java
@@ -0,0 +1,66 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ * 
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.console.authentication;
+
+import org.jboss.arquillian.graphene.page.Page;
+import static org.junit.Assert.assertEquals;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.console.AbstractConsoleTest;
+import org.keycloak.testsuite.console.page.authentication.bindings.Bindings;
+import org.keycloak.testsuite.console.page.authentication.bindings.BindingsForm.BindingsOption;
+import org.keycloak.testsuite.console.page.authentication.bindings.BindingsForm.BindingsSelect;
+
+/**
+ *
+ * @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
+ */
+public class BindingsTest extends AbstractConsoleTest {
+    
+    @Page
+    private Bindings bindingsPage;
+    
+    @Before
+    public void beforeBindingsTest() {
+        bindingsPage.navigateTo();
+    }
+    
+    @Test
+    public void bindingsTest() {
+        bindingsPage.form().select(BindingsSelect.BROWSER, BindingsOption.REGISTRATION);
+        bindingsPage.form().select(BindingsSelect.REGISTRATION, BindingsOption.RESET_CREDENTIALS);
+        bindingsPage.form().select(BindingsSelect.DIRECT_GRANT, BindingsOption.BROWSER);
+        bindingsPage.form().select(BindingsSelect.RESET_CREDENTIALS, BindingsOption.DIRECT_GRANT);
+        bindingsPage.form().save();
+        
+        assertEquals("Success! Your changes have been saved to the realm.", bindingsPage.getSuccessMessage());
+        
+        RealmRepresentation realm = testRealmResource().toRepresentation();
+        
+        assertEquals("registration", realm.getBrowserFlow());
+        assertEquals("reset credentials", realm.getRegistrationFlow());
+        assertEquals("browser", realm.getDirectGrantFlow());
+        assertEquals("direct grant", realm.getResetCredentialsFlow());
+        assertEquals("clients", realm.getClientAuthenticationFlow());
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/authentication/OTPPolicyTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/authentication/OTPPolicyTest.java
new file mode 100644
index 0000000..d9f8f89
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/authentication/OTPPolicyTest.java
@@ -0,0 +1,126 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ * 
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.console.authentication;
+
+import org.jboss.arquillian.graphene.page.Page;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.console.AbstractConsoleTest;
+import org.keycloak.testsuite.console.page.authentication.otppolicy.OTPPolicy;
+import org.keycloak.testsuite.console.page.authentication.otppolicy.OTPPolicyForm.Digits;
+import org.keycloak.testsuite.console.page.authentication.otppolicy.OTPPolicyForm.OTPHashAlg;
+import org.keycloak.testsuite.console.page.authentication.otppolicy.OTPPolicyForm.OTPType;
+
+/**
+ *
+ * @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
+ */
+public class OTPPolicyTest extends AbstractConsoleTest {
+    
+    @Page
+    private OTPPolicy otpPolicyPage;
+    
+    @Before
+    public void beforeOTPPolicyTest() {
+        otpPolicyPage.navigateTo();
+    }
+    
+    @Test
+    public void otpPolicyTest() {
+        otpPolicyPage.form().setValues(OTPType.COUNTER_BASED, OTPHashAlg.SHA256, Digits.EIGHT, "10", "50");
+        assertEquals("Success! Your changes have been saved to the realm.", otpPolicyPage.getSuccessMessage());
+        
+        RealmRepresentation realm = testRealmResource().toRepresentation();
+        assertEquals("hotp", realm.getOtpPolicyType());
+        assertEquals("HmacSHA256", realm.getOtpPolicyAlgorithm());
+        assertEquals(Integer.valueOf(8), realm.getOtpPolicyDigits());
+        assertEquals(Integer.valueOf(10), realm.getOtpPolicyLookAheadWindow());
+        assertEquals(Integer.valueOf(50), realm.getOtpPolicyInitialCounter());
+        
+        otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA512, Digits.EIGHT, "10", "40");
+        assertEquals("Success! Your changes have been saved to the realm.", otpPolicyPage.getSuccessMessage());
+        
+        realm = testRealmResource().toRepresentation();
+        assertEquals(Integer.valueOf(40), realm.getOtpPolicyPeriod());
+    }      
+    
+    @Test
+    @Ignore //KEYCLOAK-2051 when you close notification, it is not displayed again
+    public void invalidValuesTest() {
+        otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "", "30");
+        assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
+        otpPolicyPage.closeNotification();
+        otpPolicyPage.navigateTo();// workaround: input.clear() doesn't work when <input type="number" ...
+        
+        otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.EMPTY, " ", "30");
+        assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
+        otpPolicyPage.closeNotification();
+        otpPolicyPage.navigateTo();
+        
+        otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "no number", "30");
+        assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
+        otpPolicyPage.closeNotification();
+        otpPolicyPage.navigateTo();
+        
+        RealmRepresentation realm = testRealmResource().toRepresentation();
+        assertEquals(Integer.valueOf(1), realm.getOtpPolicyLookAheadWindow());
+
+        otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "1", "");
+        assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
+        otpPolicyPage.closeNotification();
+        otpPolicyPage.navigateTo();
+        
+        otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "1", " ");
+        assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
+        otpPolicyPage.closeNotification();
+        otpPolicyPage.navigateTo();
+        
+        otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "1", "no number");
+        assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
+        otpPolicyPage.closeNotification();
+        otpPolicyPage.navigateTo();
+        
+        realm = testRealmResource().toRepresentation();
+        assertEquals(Integer.valueOf(30), realm.getOtpPolicyPeriod());
+        
+        otpPolicyPage.form().setValues(OTPType.COUNTER_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "1", "");
+        assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
+        otpPolicyPage.closeNotification();
+        otpPolicyPage.navigateTo();
+        
+        otpPolicyPage.form().setValues(OTPType.COUNTER_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "1", " ");
+        assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
+        otpPolicyPage.closeNotification();
+        otpPolicyPage.navigateTo();
+        
+        otpPolicyPage.form().setValues(OTPType.COUNTER_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "1", "no number");
+        assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
+        otpPolicyPage.closeNotification();
+        otpPolicyPage.navigateTo();
+        
+        realm = testRealmResource().toRepresentation();
+        assertEquals(Integer.valueOf(0), realm.getOtpPolicyInitialCounter());
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/federation/KerberosUserFederationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/federation/KerberosUserFederationTest.java
new file mode 100644
index 0000000..47521e5
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/federation/KerberosUserFederationTest.java
@@ -0,0 +1,75 @@
+package org.keycloak.testsuite.console.federation;
+
+import static org.junit.Assert.assertEquals;
+
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.Test;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserFederationProviderRepresentation;
+import org.keycloak.testsuite.console.AbstractConsoleTest;
+import org.keycloak.testsuite.console.page.federation.CreateKerberosUserProvider;
+
+/**
+ * @author pdrozd
+ */
+public class KerberosUserFederationTest extends AbstractConsoleTest {
+
+	private static final String UNSYNCED = "UNSYNCED";
+
+	private static final String READ_ONLY = "READ_ONLY";
+
+	@Page
+	private CreateKerberosUserProvider createKerberosUserProvider;
+
+	@Test
+	public void configureKerberosProvider() {
+		createKerberosUserProvider.navigateTo();
+		createKerberosUserProvider.form().setConsoleDisplayNameInput("kerberos");
+		createKerberosUserProvider.form().setKerberosRealmInput("KEYCLOAK.ORG");
+		createKerberosUserProvider.form().setServerPrincipalInput("HTTP/localhost@KEYCLOAK.ORG");
+		createKerberosUserProvider.form().setKeyTabInput("http.keytab");
+		createKerberosUserProvider.form().setDebugEnabled(true);
+		createKerberosUserProvider.form().setAllowPasswordAuthentication(true);
+		createKerberosUserProvider.form().selectEditMode(READ_ONLY);
+		createKerberosUserProvider.form().setUpdateProfileFirstLogin(true);
+		createKerberosUserProvider.form().save();
+		assertFlashMessageSuccess();
+		RealmRepresentation realm = testRealmResource().toRepresentation();
+		UserFederationProviderRepresentation ufpr = realm.getUserFederationProviders().get(0);
+		assertKerberosSetings(ufpr, "KEYCLOAK.ORG", "HTTP/localhost@KEYCLOAK.ORG", "http.keytab", "true", "true", "true");
+	}
+
+	@Test
+	public void invalidSettingsTest() {
+		createKerberosUserProvider.navigateTo();
+		createKerberosUserProvider.form().setConsoleDisplayNameInput("kerberos");
+		createKerberosUserProvider.form().setServerPrincipalInput("HTTP/localhost@KEYCLOAK.ORG");
+		createKerberosUserProvider.form().setKeyTabInput("http.keytab");
+		createKerberosUserProvider.form().setDebugEnabled(true);
+		createKerberosUserProvider.form().setAllowPasswordAuthentication(true);
+		createKerberosUserProvider.form().selectEditMode(UNSYNCED);
+		createKerberosUserProvider.form().setUpdateProfileFirstLogin(true);
+		createKerberosUserProvider.form().save();
+		assertFlashMessageDanger();
+		createKerberosUserProvider.form().setServerPrincipalInput("");
+		createKerberosUserProvider.form().setKerberosRealmInput("KEYCLOAK.ORG");;
+		createKerberosUserProvider.form().save();
+		assertFlashMessageDanger();
+		createKerberosUserProvider.form().setServerPrincipalInput("HTTP/localhost@KEYCLOAK.ORG");;
+		createKerberosUserProvider.form().setKeyTabInput("");
+		createKerberosUserProvider.form().save();
+		assertFlashMessageDanger();		
+		createKerberosUserProvider.form().setKeyTabInput("http.keytab");;
+		createKerberosUserProvider.form().save();
+		assertFlashMessageSuccess();
+	}
+
+	private void assertKerberosSetings(UserFederationProviderRepresentation ufpr, String kerberosRealm, String serverPrincipal, String keyTab, String debug, String useKerberosForPasswordAuthentication, String updateProfileFirstLogin) {
+		assertEquals(kerberosRealm, ufpr.getConfig().get("kerberosRealm"));
+		assertEquals(serverPrincipal, ufpr.getConfig().get("serverPrincipal"));
+		assertEquals(keyTab, ufpr.getConfig().get("keyTab"));
+		assertEquals(debug, ufpr.getConfig().get("debug"));
+		assertEquals(useKerberosForPasswordAuthentication, ufpr.getConfig().get("allowKerberosAuthentication"));
+		assertEquals(updateProfileFirstLogin, ufpr.getConfig().get("updateProfileFirstLogin"));
+	}
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/federation/LdapUserFederationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/federation/LdapUserFederationTest.java
index e70da46..e040362 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/federation/LdapUserFederationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/federation/LdapUserFederationTest.java
@@ -1,71 +1,192 @@
 package org.keycloak.testsuite.console.federation;
 
-import org.jboss.arquillian.graphene.page.Page;
-import org.junit.*;
-import org.keycloak.models.LDAPConstants;
-
-import org.keycloak.representations.idm.UserRepresentation;
-import org.keycloak.testsuite.console.AbstractConsoleTest;
-import org.keycloak.testsuite.console.page.federation.LdapUserProviderForm;
-import org.keycloak.testsuite.console.page.federation.UserFederation;
-import org.keycloak.testsuite.console.page.users.Users;
-import org.keycloak.testsuite.util.LDAPTestConfiguration;
+import static org.junit.Assert.assertEquals;
 
-import java.util.Map;
+import java.util.Properties;
 
-import static org.junit.Assert.assertTrue;
-import static org.keycloak.representations.idm.CredentialRepresentation.PASSWORD;
-import static org.keycloak.testsuite.admin.Users.setPasswordFor;
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.Test;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserFederationProviderRepresentation;
+import org.keycloak.testsuite.console.AbstractConsoleTest;
+import org.keycloak.testsuite.console.page.federation.CreateLdapUserProvider;
+import org.keycloak.util.ldap.LDAPEmbeddedServer;
 
 /**
- * Created by fkiss.
+ * @author fkiss, pdrozd
  */
 public class LdapUserFederationTest extends AbstractConsoleTest {
 
-    @Page
-    private LdapUserProviderForm ldapUserProviderForm;
+    private static final String UNSYNCED = "UNSYNCED";
 
-    @Page
-    private UserFederation userFederationPage;
+    private static final String READ_ONLY = "READ_ONLY";
+
+    private static final String RED_HAT_DIRECTORY_SERVER = "Red Hat Directory Server";
+
+    private static final String WRITABLE = "WRITABLE";
+
+    private static final String ACTIVE_DIRECTORY = "Active Directory";
 
     @Page
-    private Users usersPage;
+    private CreateLdapUserProvider createLdapUserProvider;
 
-    @Before
-    public void beforeTestLdapUserFederation() {
-        //configure().userFederation();
+    @Test
+    public void configureAdProvider() {
+        createLdapUserProvider.navigateTo();
+        createLdapUserProvider.form().selectVendor(ACTIVE_DIRECTORY);
+        createLdapUserProvider.form().setConsoleDisplayNameInput("ldap");
+        createLdapUserProvider.form().selectEditMode(WRITABLE);
+        createLdapUserProvider.form().setLdapConnectionUrlInput("ldap://localhost:389");
+        createLdapUserProvider.form().setLdapBindDnInput("KEYCLOAK/Administrator");
+        createLdapUserProvider.form().setLdapUserDnInput("ou=People,dc=keycloak,dc=org");
+        createLdapUserProvider.form().setLdapBindCredentialInput("secret");
+        createLdapUserProvider.form().setAccountAfterPasswordUpdateEnabled(false);
+        // enable kerberos
+        createLdapUserProvider.form().setAllowKerberosAuthEnabled(true);
+        createLdapUserProvider.form().setKerberosRealmInput("KEYCLOAK.ORG");
+        createLdapUserProvider.form().setServerPrincipalInput("HTTP/localhost@KEYCLOAK.ORG");
+        createLdapUserProvider.form().setKeyTabInput("http.keytab");
+        createLdapUserProvider.form().setDebugEnabled(true);
+        createLdapUserProvider.form().save();
+        assertFlashMessageSuccess();
+
+        RealmRepresentation realm = testRealmResource().toRepresentation();
+        UserFederationProviderRepresentation ufpr = realm.getUserFederationProviders().get(0);
+        assertLdapProviderSetting(ufpr, "ldap", 0, WRITABLE, "false", "ad", "1", "true", "true", "false");
+        assertLdapBasicMapping(ufpr, "cn", "cn", "objectGUID", "person, organizationalPerson, user",
+                "ou=People,dc=keycloak,dc=org");
+        assertLdapSyncSetings(ufpr, "1000", 0, 0);
+        assertLdapKerberosSetings(ufpr, "KEYCLOAK.ORG", "HTTP/localhost@KEYCLOAK.ORG", "http.keytab", "true", "false");
     }
 
-    @Ignore
     @Test
-    public void addAndConfigureProvider() {
-        adminConsolePage.navigateTo();
-        testRealmLoginPage.form().login(testUser);
+    public void configureRhdsProvider() {
+        createLdapUserProvider.navigateTo();
+        createLdapUserProvider.form().selectVendor(RED_HAT_DIRECTORY_SERVER);
+        createLdapUserProvider.form().setConsoleDisplayNameInput("ldap");
+        createLdapUserProvider.form().selectEditMode(READ_ONLY);
+        createLdapUserProvider.form().setLdapConnectionUrlInput("ldap://localhost:389");
+        createLdapUserProvider.form().setLdapBindDnInput("uid=admin,ou=system");
+        createLdapUserProvider.form().setLdapUserDnInput("ou=People,dc=keycloak,dc=org");
+        createLdapUserProvider.form().setLdapBindCredentialInput("secret");
+        createLdapUserProvider.form().save();
+        assertFlashMessageSuccess();
+
+        RealmRepresentation realm = testRealmResource().toRepresentation();
+        UserFederationProviderRepresentation ufpr = realm.getUserFederationProviders().get(0);
+        assertLdapProviderSetting(ufpr, "ldap", 0, READ_ONLY, "false", "rhds", "1", "true", "true", "true");
+        assertLdapBasicMapping(ufpr, "uid", "uid", "nsuniqueid", "inetOrgPerson, organizationalPerson",
+                "ou=People,dc=keycloak,dc=org");
+        assertLdapSyncSetings(ufpr, "1000", 0, 0);
+    }
 
-        String name = "ldapname";
+    @Test
+    public void invalidSettingsTest() {
+        createLdapUserProvider.navigateTo();
+        createLdapUserProvider.form().selectVendor(ACTIVE_DIRECTORY);
+        createLdapUserProvider.form().setConsoleDisplayNameInput("ldap");
+        createLdapUserProvider.form().selectEditMode(UNSYNCED);
+        createLdapUserProvider.form().setLdapBindDnInput("uid=admin,ou=system");
+        createLdapUserProvider.form().setLdapUserDnInput("ou=People,dc=keycloak,dc=org");
+        createLdapUserProvider.form().setLdapBindCredentialInput("secret");
+        createLdapUserProvider.form().save();
+        assertFlashMessageDanger();
+        createLdapUserProvider.form().setLdapUserDnInput("");
+        createLdapUserProvider.form().setLdapConnectionUrlInput("ldap://localhost:389");
+        createLdapUserProvider.form().save();
+        assertFlashMessageDanger();
+        createLdapUserProvider.form().setLdapUserDnInput("ou=People,dc=keycloak,dc=org");
+        createLdapUserProvider.form().setLdapBindDnInput("");
+        createLdapUserProvider.form().save();
+        assertFlashMessageDanger();
+        createLdapUserProvider.form().setLdapBindDnInput("uid=admin,ou=system");
+        createLdapUserProvider.form().setLdapBindCredentialInput("");
+        createLdapUserProvider.form().save();
+        assertFlashMessageDanger();
+        createLdapUserProvider.form().setLdapBindCredentialInput("secret");
+        createLdapUserProvider.form().save();
+        assertFlashMessageSuccess();
+    }
+
+    @Test
+    public void testConnection() throws Exception {
+        createLdapUserProvider.navigateTo();
+        createLdapUserProvider.form().selectVendor("Other");
+        createLdapUserProvider.form().setConsoleDisplayNameInput("ldap");
+        createLdapUserProvider.form().selectEditMode(WRITABLE);
+        createLdapUserProvider.form().setLdapConnectionUrlInput("ldap://localhost:10389");
+        createLdapUserProvider.form().setLdapBindDnInput("uid=admin,ou=system");
+        createLdapUserProvider.form().setLdapUserDnInput("ou=People,dc=keycloak,dc=org");
+        createLdapUserProvider.form().setLdapBindCredentialInput("secret");
+        createLdapUserProvider.form().setAccountAfterPasswordUpdateEnabled(true);
+        createLdapUserProvider.form().save();
+        assertFlashMessageSuccess();
+        LDAPEmbeddedServer ldapServer = null;
+        try {
+            ldapServer = startEmbeddedLdapServer();
+            createLdapUserProvider.form().testConnection();
+            assertFlashMessageSuccess();
+            createLdapUserProvider.form().testAuthentication();
+            assertFlashMessageSuccess();
+            createLdapUserProvider.form().synchronizeAllUsers();
+            assertFlashMessageSuccess();
+            createLdapUserProvider.form().setLdapBindCredentialInput("secret1");
+            createLdapUserProvider.form().testAuthentication();
+            assertFlashMessageDanger();
+        } finally {
+            if (ldapServer != null) {
+                ldapServer.stop();
+            }
+        }
+    }
 
-        String LDAP_CONNECTION_PROPERTIES_LOCATION = "ldap/ldap-connection.properties";
-        LDAPTestConfiguration ldapTestConfiguration = LDAPTestConfiguration.readConfiguration(LDAP_CONNECTION_PROPERTIES_LOCATION);
+    private void assertLdapProviderSetting(UserFederationProviderRepresentation ufpr, String name, int priority,
+            String editMode, String syncRegistrations, String vendor, String searchScope, String connectionPooling,
+            String pagination, String enableAccountAfterPasswordUpdate) {
+        assertEquals(name, ufpr.getDisplayName());
+        assertEquals(priority, ufpr.getPriority());
+        assertEquals(editMode, ufpr.getConfig().get("editMode"));
+        assertEquals(syncRegistrations, ufpr.getConfig().get("syncRegistrations"));
+        assertEquals(vendor, ufpr.getConfig().get("vendor"));
+        assertEquals(searchScope, ufpr.getConfig().get("searchScope"));
+        assertEquals(connectionPooling, ufpr.getConfig().get("connectionPooling"));
+        assertEquals(pagination, ufpr.getConfig().get("pagination"));
+        assertEquals(enableAccountAfterPasswordUpdate, ufpr.getConfig().get("userAccountControlsAfterPasswordUpdate"));
+    }
 
-        UserRepresentation newUser = new UserRepresentation();
-        String testUsername = "defaultrole tester";
-        newUser.setUsername(testUsername);
-        setPasswordFor(newUser, PASSWORD);
+    private void assertLdapBasicMapping(UserFederationProviderRepresentation ufpr, String usernameLdapAttribute,
+            String rdnLdapAttr, String uuidLdapAttr, String userObjectClasses, String userDN) {
+        assertEquals(usernameLdapAttribute, ufpr.getConfig().get("usernameLDAPAttribute"));
+        assertEquals(rdnLdapAttr, ufpr.getConfig().get("rdnLDAPAttribute"));
+        assertEquals(uuidLdapAttr, ufpr.getConfig().get("uuidLDAPAttribute"));
+        assertEquals(userObjectClasses, ufpr.getConfig().get("userObjectClasses"));
+        assertEquals(userDN, ufpr.getConfig().get("usersDn"));
+    }
 
-        Map<String,String> ldapConfig = ldapTestConfiguration.getLDAPConfig();
+    private void assertLdapKerberosSetings(UserFederationProviderRepresentation ufpr, String kerberosRealm,
+            String serverPrincipal, String keyTab, String debug, String useKerberosForPasswordAuthentication) {
+        assertEquals(kerberosRealm, ufpr.getConfig().get("kerberosRealm"));
+        assertEquals(serverPrincipal, ufpr.getConfig().get("serverPrincipal"));
+        assertEquals(keyTab, ufpr.getConfig().get("keyTab"));
+        assertEquals(debug, ufpr.getConfig().get("debug"));
+        assertEquals(useKerberosForPasswordAuthentication,
+                ufpr.getConfig().get("useKerberosForPasswordAuthentication"));
+    }
 
-        //addLdapProviderTest
-        configure().userFederation();
-        userFederationPage.addProvider("ldap");
-        ldapUserProviderForm.configureLdap(ldapConfig.get(LDAPConstants.LDAP_PROVIDER), ldapConfig.get(LDAPConstants.EDIT_MODE), ldapConfig.get(LDAPConstants.VENDOR), ldapConfig.get(LDAPConstants.CONNECTION_URL), ldapConfig.get(LDAPConstants.USERS_DN), ldapConfig.get(LDAPConstants.BIND_DN), ldapConfig.get(LDAPConstants.BIND_CREDENTIAL));
+    private void assertLdapSyncSetings(UserFederationProviderRepresentation ufpr, String batchSize,
+            int periodicFullSync, int periodicChangedUsersSync) {
+        assertEquals(batchSize, ufpr.getConfig().get("batchSizeForSync"));
+        assertEquals(periodicFullSync, ufpr.getFullSyncPeriod());
+        assertEquals(periodicChangedUsersSync, ufpr.getChangedSyncPeriod());
     }
 
-    @Ignore
-    @Test
-    public void caseSensitiveSearch() {
-        // This should fail for now due to case-sensitivity
-        adminConsolePage.navigateTo();
-        testRealmLoginPage.form().login("johnKeycloak", "Password1");
-        assertTrue(flashMessage.getText(), flashMessage.isDanger());
+    private LDAPEmbeddedServer startEmbeddedLdapServer() throws Exception {
+        Properties defaultProperties = new Properties();
+        defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_DSF, LDAPEmbeddedServer.DSF_INMEMORY);
+        defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_LDIF_FILE, "classpath:ldap/users.ldif");
+        LDAPEmbeddedServer ldapEmbeddedServer = new LDAPEmbeddedServer(defaultProperties);
+        ldapEmbeddedServer.init();
+        ldapEmbeddedServer.start();
+        return ldapEmbeddedServer;
     }
-}
\ No newline at end of file
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java
new file mode 100644
index 0000000..41c1740
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java
@@ -0,0 +1,90 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ * 
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.migration;
+
+import java.util.List;
+import static org.junit.Assert.*;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.ClientsResource;
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.testsuite.AbstractKeycloakTest;
+import org.keycloak.testsuite.arquillian.migration.Migration;
+
+/**
+ * @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
+ */
+public class MigrationTest extends AbstractKeycloakTest {
+    
+    @Override
+    public void addTestRealms(List<RealmRepresentation> testRealms) {
+        log.info("Adding no test realms for migration test. Test realm should be migrated from previous vesrion.");
+    }
+    
+    @Test
+    @Migration(versionFrom = "1.6.1.Final")
+    public void migration16Test() {
+        RealmResource realmResource = adminClient.realms().realm("Migration");
+        RealmRepresentation realmRep = realmResource.toRepresentation();
+        assertEquals("Migration", realmRep.getRealm());
+        
+        List<RoleRepresentation> realmRoles = realmResource.roles().list();
+        assertEquals(1, realmRoles.size());
+        assertEquals("offline_access", realmRoles.get(0).getName());
+        
+        for (ClientRepresentation client : realmResource.clients().findAll()) {
+            final String clientId = client.getClientId();
+            switch (clientId) {
+                case "realm-management":
+                    assertEquals(13, realmResource.clients().get(client.getId()).roles().list().size());
+                    break;
+                case "security-admin-console":
+                    assertEquals(0, realmResource.clients().get(client.getId()).roles().list().size());
+                    break;
+                case "broker":
+                    assertEquals(1, realmResource.clients().get(client.getId()).roles().list().size());
+                    break;
+                case "account":
+                    assertEquals(2, realmResource.clients().get(client.getId()).roles().list().size());
+                    break;
+                default:
+                    fail("Migrated realm contains unexpected client " + clientId);
+                    break;
+            }
+        }
+    }
+    
+    @Test
+    @Migration(versionFrom = "1.5.1.Final")
+    @Ignore
+    public void migration15Test() {
+        for (RealmRepresentation realm : adminClient.realms().findAll()) {
+            System.out.println(realm.getRealm());
+        }
+        
+        //TODO
+    }
+
+}
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 bc82ac7..ad3f626 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
@@ -21,12 +21,20 @@
     <!-- PREVIOUS VERSIONS KEYCLOAK FOR MIGRATION TESTS -->
     <!-- IT HAS TO BE LISTED ABOWE KEYCLOAK AUTH SERVERS -->
     
-    <container qualifier="keycloak-1.6.0.Final" mode="suite" >
+    <container qualifier="keycloak-1.6.1.Final" mode="suite" >
         <configuration>
             <property name="enabled">${migration.kc16}</property>
             <property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
-            <property name="jbossHome">${keycloak-1.6.0.Final.home}</property>
-            <property name="javaVmArguments">-Djboss.socket.binding.port-offset=${auth.server.port.offset} -Xms64m -Xmx512m -XX:MaxPermSize=256m</property>
+            <property name="jbossHome">${keycloak.migration.home}</property>
+            <property name="javaVmArguments">
+                -Dkeycloak.migration.action=import 
+                -Dkeycloak.migration.provider=singleFile
+                -Dkeycloak.migration.file=${keycloak.migration.file}
+                -Dkeycloak.migration.strategy=OVERWRITE_EXISTING
+                -Dkeycloak.migration.realmName=Migration
+                -Djboss.socket.binding.port-offset=${auth.server.port.offset} 
+                -Xms64m -Xmx512m -XX:MaxPermSize=256m
+            </property>
             <property name="managementPort">${auth.server.management.port}</property>
             <property name="startupTimeoutInSeconds">${startup.timeout.sec}</property>
         </configuration>
@@ -36,8 +44,16 @@
         <configuration>
             <property name="enabled">${migration.kc15}</property>
             <property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
-            <property name="jbossHome">${keycloak-1.5.1.Final.home}</property>
-            <property name="javaVmArguments">-Djboss.socket.binding.port-offset=${auth.server.port.offset} -Xms64m -Xmx512m -XX:MaxPermSize=256m</property>
+            <property name="jbossHome">${keycloak.migration.home}</property>
+            <property name="javaVmArguments">
+                -Dkeycloak.migration.action=import 
+                -Dkeycloak.migration.provider=singleFile
+                -Dkeycloak.migration.file=${keycloak.migration.file}
+                -Dkeycloak.migration.strategy=OVERWRITE_EXISTING
+                -Dkeycloak.migration.realmName=Migration
+                -Djboss.socket.binding.port-offset=${auth.server.port.offset} 
+                -Xms64m -Xmx512m -XX:MaxPermSize=256m
+            </property>
             <property name="managementPort">${auth.server.management.port}</property>
             <property name="startupTimeoutInSeconds">${startup.timeout.sec}</property>
         </configuration>
@@ -47,7 +63,7 @@
         <configuration>
             <property name="enabled">${migration.kc14}</property>
             <property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
-            <property name="jbossHome">${keycloak-1.4.0.Final.home}</property>
+            <property name="jbossHome">${keycloak.migration.home}</property>
             <property name="javaVmArguments">-Djboss.socket.binding.port-offset=${auth.server.port.offset} -Xms64m -Xmx512m -XX:MaxPermSize=256m</property>
             <property name="managementPort">${auth.server.management.port}</property>
             <property name="startupTimeoutInSeconds">${startup.timeout.sec}</property>
@@ -58,7 +74,7 @@
         <configuration>
             <property name="enabled">${migration.kc13}</property>
             <property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
-            <property name="jbossHome">${keycloak-1.3.1.Final.home}</property>
+            <property name="jbossHome">${keycloak.migration.home}</property>
             <property name="javaVmArguments">-Djboss.socket.binding.port-offset=${auth.server.port.offset} -Xms64m -Xmx512m -XX:MaxPermSize=256m</property>
             <property name="managementPort">${auth.server.management.port}</property>
             <property name="startupTimeoutInSeconds">${startup.timeout.sec}</property>
@@ -69,7 +85,7 @@
         <configuration>
             <property name="enabled">${migration.kc12}</property>
             <property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
-            <property name="jbossHome">${keycloak-1.2.0.Final.home}</property>
+            <property name="jbossHome">${keycloak.migration.homee}</property>
             <property name="javaVmArguments">-Djboss.socket.binding.port-offset=${auth.server.port.offset} -Xms64m -Xmx512m -XX:MaxPermSize=256m</property>
             <property name="managementPort">${auth.server.management.port}</property>
             <property name="startupTimeoutInSeconds">${startup.timeout.sec}</property>
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/clientreg-test/saml-entity-descriptor.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/clientreg-test/saml-entity-descriptor.xml
new file mode 100644
index 0000000..b00ab25
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/clientreg-test/saml-entity-descriptor.xml
@@ -0,0 +1,82 @@
+<EntityDescriptor
+    xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+    entityID="loadbalancer-9.siroe.com">
+    <SPSSODescriptor
+        AuthnRequestsSigned="false"
+        WantAssertionsSigned="false"
+        protocolSupportEnumeration=
+            "urn:oasis:names:tc:SAML:2.0:protocol">
+        <KeyDescriptor use="signing">
+            <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
+                <X509Data>
+                    <X509Certificate>
+MIICYDCCAgqgAwIBAgICBoowDQYJKoZIhvcNAQEEBQAwgZIxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
+EwpDYWxpZm9ybmlhMRQwEgYDVQQHEwtTYW50YSBDbGFyYTEeMBwGA1UEChMVU3VuIE1pY3Jvc3lz
+dGVtcyBJbmMuMRowGAYDVQQLExFJZGVudGl0eSBTZXJ2aWNlczEcMBoGA1UEAxMTQ2VydGlmaWNh
+dGUgTWFuYWdlcjAeFw0wNjExMDIxOTExMzRaFw0xMDA3MjkxOTExMzRaMDcxEjAQBgNVBAoTCXNp
+cm9lLmNvbTEhMB8GA1UEAxMYbG9hZGJhbGFuY2VyLTkuc2lyb2UuY29tMIGfMA0GCSqGSIb3DQEB
+AQUAA4GNADCBiQKBgQCjOwa5qoaUuVnknqf5pdgAJSEoWlvx/jnUYbkSDpXLzraEiy2UhvwpoBgB
+EeTSUaPPBvboCItchakPI6Z/aFdH3Wmjuij9XD8r1C+q//7sUO0IGn0ORycddHhoo0aSdnnxGf9V
+tREaqKm9dJ7Yn7kQHjo2eryMgYxtr/Z5Il5F+wIDAQABo2AwXjARBglghkgBhvhCAQEEBAMCBkAw
+DgYDVR0PAQH/BAQDAgTwMB8GA1UdIwQYMBaAFDugITflTCfsWyNLTXDl7cMDUKuuMBgGA1UdEQQR
+MA+BDW1hbGxhQHN1bi5jb20wDQYJKoZIhvcNAQEEBQADQQB/6DOB6sRqCZu2OenM9eQR0gube85e
+nTTxU4a7x1naFxzYXK1iQ1vMARKMjDb19QEJIEJKZlDK4uS7yMlf1nFS
+                    </X509Certificate>
+                </X509Data>
+            </KeyInfo>
+        </KeyDescriptor>
+        <KeyDescriptor use="encryption">
+            <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
+                <X509Data>
+                    <X509Certificate>
+MIICTDCCAfagAwIBAgICBo8wDQYJKoZIhvcNAQEEBQAwgZIxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
+EwpDYWxpZm9ybmlhMRQwEgYDVQQHEwtTYW50YSBDbGFyYTEeMBwGA1UEChMVU3VuIE1pY3Jvc3lz
+dGVtcyBJbmMuMRowGAYDVQQLExFJZGVudGl0eSBTZXJ2aWNlczEcMBoGA1UEAxMTQ2VydGlmaWNh
+dGUgTWFuYWdlcjAeFw0wNjExMDcyMzU2MTdaFw0xMDA4MDMyMzU2MTdaMCMxITAfBgNVBAMTGGxv
+YWRiYWxhbmNlci05LnNpcm9lLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAw574iRU6
+HsSO4LXW/OGTXyfsbGv6XRVOoy3v+J1pZ51KKejcDjDJXNkKGn3/356AwIaqbcymWd59T0zSqYfR
+Hn+45uyjYxRBmVJseLpVnOXLub9jsjULfGx0yjH4w+KsZSZCXatoCHbj/RJtkzuZY6V9to/hkH3S
+InQB4a3UAgMCAwEAAaNgMF4wEQYJYIZIAYb4QgEBBAQDAgZAMA4GA1UdDwEB/wQEAwIE8DAfBgNV
+HSMEGDAWgBQ7oCE35Uwn7FsjS01w5e3DA1CrrjAYBgNVHREEETAPgQ1tYWxsYUBzdW4uY29tMA0G
+CSqGSIb3DQEBBAUAA0EAMlbfBg/ff0Xkv4DOR5LEqmfTZKqgdlD81cXynfzlF7XfnOqI6hPIA90I
+x5Ql0ejivIJAYcMGUyA+/YwJg2FGoA==
+                    </X509Certificate>
+                </X509Data>
+            </KeyInfo>
+            <EncryptionMethod Algorithm=
+                "https://www.w3.org/2001/04/xmlenc#aes128-cbc">
+                <KeySize xmlns="https://www.w3.org/2001/04/xmlenc#">128</KeySize>
+            </EncryptionMethod>
+        </KeyDescriptor>
+        <SingleLogoutService
+            Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
+            Location="https://LoadBalancer-9.siroe.com:3443/federation/SPSloRedirect/metaAlias/sp"
+            ResponseLocation="https://LoadBalancer-9.siroe.com:3443/federation/SPSloRedirect/metaAlias/sp"/>
+        <SingleLogoutService
+            Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
+            Location="https://LoadBalancer-9.siroe.com:3443/federation/SPSloSoap/metaAlias/sp"/>
+       <ManageNameIDService
+            Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
+            Location="https://LoadBalancer-9.siroe.com:3443/federation/SPMniRedirect/metaAlias/sp"
+            ResponseLocation="https://LoadBalancer-9.siroe.com:3443/federation/SPMniRedirect/metaAlias/sp"/>
+        <ManageNameIDService
+            Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
+            Location="https://LoadBalancer-9.siroe.com:3443/federation/SPMniSoap/metaAlias/sp"
+            ResponseLocation="https://LoadBalancer-9.siroe.com:3443/federation/SPMniSoap/metaAlias/sp"/>
+        <NameIDFormat>
+            urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
+        </NameIDFormat>
+        <NameIDFormat>
+            urn:oasis:names:tc:SAML:2.0:nameid-format:transient
+        </NameIDFormat>
+        <AssertionConsumerService
+            isDefault="true"
+            index="0"
+            Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
+            Location="https://LoadBalancer-9.siroe.com:3443/federation/Consumer/metaAlias/sp"/>
+        <AssertionConsumerService
+            index="1"
+            Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+            Location="https://LoadBalancer-9.siroe.com:3443/federation/Consumer/metaAlias/sp"/>
+    </SPSSODescriptor>
+</EntityDescriptor>
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/ldap/users.ldif b/testsuite/integration-arquillian/tests/base/src/test/resources/ldap/users.ldif
new file mode 100644
index 0000000..176e19b
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/ldap/users.ldif
@@ -0,0 +1,20 @@
+dn: dc=keycloak,dc=org
+objectclass: dcObject
+objectclass: organization
+o: Keycloak
+dc: Keycloak
+
+dn: ou=People,dc=keycloak,dc=org
+objectclass: top
+objectclass: organizationalUnit
+ou: People
+
+dn: ou=RealmRoles,dc=keycloak,dc=org
+objectclass: top
+objectclass: organizationalUnit
+ou: RealmRoles
+
+dn: ou=FinanceRoles,dc=keycloak,dc=org
+objectclass: top
+objectclass: organizationalUnit
+ou: FinanceRoles
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-15.json b/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-15.json
new file mode 100644
index 0000000..86e4606
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-15.json
@@ -0,0 +1,751 @@
+{
+  "id" : "9c3a9824-cc8b-46f6-8922-cd576a92850f",
+  "realm" : "Migration",
+  "notBefore" : 0,
+  "accessTokenLifespan" : 300,
+  "ssoSessionIdleTimeout" : 1800,
+  "ssoSessionMaxLifespan" : 36000,
+  "accessCodeLifespan" : 60,
+  "accessCodeLifespanUserAction" : 300,
+  "accessCodeLifespanLogin" : 1800,
+  "enabled" : true,
+  "sslRequired" : "external",
+  "registrationAllowed" : false,
+  "registrationEmailAsUsername" : false,
+  "rememberMe" : false,
+  "verifyEmail" : false,
+  "resetPasswordAllowed" : false,
+  "editUsernameAllowed" : false,
+  "bruteForceProtected" : false,
+  "maxFailureWaitSeconds" : 900,
+  "minimumQuickLoginWaitSeconds" : 60,
+  "waitIncrementSeconds" : 60,
+  "quickLoginCheckMilliSeconds" : 1000,
+  "maxDeltaTimeSeconds" : 43200,
+  "failureFactor" : 30,
+  "privateKey" : "MIIEpAIBAAKCAQEA29+/bYOEg+RFlDgKjX0nv+UMkV8X06E1XvRobuQjXKOV613VJIa1F/nGabXthkM3tC7DadJ5y1tBwhF+bJzMA4w38zNfJdjEp3DRND6ypUn0SJZrSw6l3u3w+s5uemgTWUZk463Xr3HbDxtnG+4t5GuHA2Oq6O2OLniVZKbDTpgF1HxzCBQiAxi2jNJm3tMlTdN6D/nV3Rwp2T1250T3ldkM3TDK/Nlup3oOejy+qRGEmh+omuABOOJ8icCULZ5S2AbiqfojP5ZN3WEpyCqcQvsdop4IawUbTDyy9BCE2K5CCZ6ZgQaSnpJZGUy91crPJXnI4tlg5Mh88l8aSrBLsQIDAQABAoIBAA5J7SPNzzfPBuKJ/c2SG5ox5W4xEthS+qfwFDVYqB+mFeEU2PwlsPEc71MBWq1GAwG3pEVlQzr+9DgLcP7X9b4pR52LchyAiM8k2sOda3ioZLKu68wV6JujNOznq3BTASblFztgmcqyCH1j14COKvdUMZL70CiQ/5NvjK3c1IZv5d/S9B7Qhd2o/6cO51xIodE87Lc4Pghq8cQ/AJJUJokyFtjkCpTNAYxcZgyiEMNbyjrbNMMEpiuspZ50eRbi7SOKOg6mSjwuTeK0cQ57JDuMhE/iyaMwh98uqSTccqeKS672z+7QCu89ce1YZMnWtjfwKEiIcTWB71pvy2gGwgECgYEA9Afv+5Zop4j1kmZvQcdr+UpW3Ia91nNelvlkMYPMrsC24xwrGhO9Hx76VxdBFCzDuYBIyOzbPLV7kFojSKmcWB6hb/S/j6eMd46ZetycrfH5sRpJHmqJpGZiARrWTLsFRNDwi2jwEl2qt3wkq/IBvuzNt9bwbnsajgRVSVWUBtECgYEA5qh+hl1f8R6KcrUro9kSSxjmlqzSIeAYNJ0VJkr2ZjRd984xTRSnUcuVJnbfNgfmywCB9s7QGgcMrs9BejKuP1bq2hnjjA1WOvz0Dq1FRw3wqYSZWHtVO2h/QDaKIcGjQ/PyWAyrOTBaL+bzKrNO66L7CQK63A4/Gj7QivFA7uECgYEAmYW81pyDbpLdW6MR72IUbZr1Fnu2RooCQhzXiccPKAmZhTudaiRs4H1OpSe+C4E2CSfJoo5QRtstx1zNwdLixxVOHu7s7OVNm5GcwQy1jUEkAuU0huwjd8fpdCR8GX23DNod2rbEAennktOJBpuTuZekvDl+vSK5TAsx1JcAL2ECgYB7rPTKjt6Wps2NW98eZ5ILejqJp/iz+TiBXYitk5wyiPmpmYGN1vkwPnymty5QBkSVrJwC/jlO+2CtiquNHgeYJr6eWytLOQt3bZJfHED9LFhSTKr8aoT06b7xa0z9dJpaIT9cPs7AR1DURn0z9Bjo9+aqmjAfNfRX2j5vgZRTgQKBgQC7+9bt4yZ0MAxJYTMVqU/LnyjPuDrgXZJYw5ZYO6r5xF0mdovE9+lY6I8OeAUg428Zk8mxMYeqOFUHF8nVBxofHrZbXR2eJxJLRO8f2GPRFYanA9MNe1Jc0WV5bi1gF+ifC0j//W1kGxCHJX1OeMSV/h8r3OaIHEwuu30ZLHFxRg==",
+  "publicKey" : "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA29+/bYOEg+RFlDgKjX0nv+UMkV8X06E1XvRobuQjXKOV613VJIa1F/nGabXthkM3tC7DadJ5y1tBwhF+bJzMA4w38zNfJdjEp3DRND6ypUn0SJZrSw6l3u3w+s5uemgTWUZk463Xr3HbDxtnG+4t5GuHA2Oq6O2OLniVZKbDTpgF1HxzCBQiAxi2jNJm3tMlTdN6D/nV3Rwp2T1250T3ldkM3TDK/Nlup3oOejy+qRGEmh+omuABOOJ8icCULZ5S2AbiqfojP5ZN3WEpyCqcQvsdop4IawUbTDyy9BCE2K5CCZ6ZgQaSnpJZGUy91crPJXnI4tlg5Mh88l8aSrBLsQIDAQAB",
+  "certificate" : "MIICoTCCAYkCBgFQs81zNDANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAlNaWdyYXRpb24wHhcNMTUxMDI5MTMzMTM3WhcNMjUxMDI5MTMzMzE3WjAUMRIwEAYDVQQDDAlNaWdyYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDb379tg4SD5EWUOAqNfSe/5QyRXxfToTVe9Ghu5CNco5XrXdUkhrUX+cZpte2GQze0LsNp0nnLW0HCEX5snMwDjDfzM18l2MSncNE0PrKlSfRIlmtLDqXe7fD6zm56aBNZRmTjrdevcdsPG2cb7i3ka4cDY6ro7Y4ueJVkpsNOmAXUfHMIFCIDGLaM0mbe0yVN03oP+dXdHCnZPXbnRPeV2QzdMMr82W6neg56PL6pEYSaH6ia4AE44nyJwJQtnlLYBuKp+iM/lk3dYSnIKpxC+x2inghrBRtMPLL0EITYrkIJnpmBBpKeklkZTL3Vys8lecji2WDkyHzyXxpKsEuxAgMBAAEwDQYJKoZIhvcNAQELBQADggEBALaDK+wutEjdgY3Ux06Amp0k5qK16dz4jn+QKjdKPB1yThfzY1pisuyCUXPBlkn1OjB5ZvYl6ouwdNXgB8aeblbHZoyXh9ODeywi1xZd7pGxNXSfx0UzRk/YEEy0DAi9pxTyRYxiZ6/XJalS9PembTQvj+mVKqg1SDv7dyv4byvndEYSaUISrtGGrM3bb68PW4zInD793PJYWDSVxmEPOYtdgBJv4HAhPIJhjw15EOGlPv5QxW9P76OgISCutHaEe3UDP+TzIBBxYQFb1ZXA6ob3TFga78mFAkY4g98gEC11QSvZqhaRtLAz6PEisHRV+xDJVROgQ4Qew4qKgwE0gGE=",
+  "codeSecret" : "32f8634c-2be2-4d4d-8118-f4f7fee80b9f",
+  "roles" : {
+    "client" : {
+      "realm-management" : [ {
+        "id" : "dad7b3a4-b533-47c8-aba5-32e6429865a2",
+        "name" : "manage-identity-providers",
+        "description" : "${role_manage-identity-providers}",
+        "composite" : false
+      }, {
+        "id" : "a1dd3971-3906-4f4a-b4cd-3a198d2d7150",
+        "name" : "view-users",
+        "description" : "${role_view-users}",
+        "composite" : false
+      }, {
+        "id" : "6c2d766f-cfa5-4cae-b1ca-81f1f9f242c8",
+        "name" : "view-clients",
+        "description" : "${role_view-clients}",
+        "composite" : false
+      }, {
+        "id" : "60bb5b3e-8067-43fe-803e-a7e367967c7c",
+        "name" : "manage-realm",
+        "description" : "${role_manage-realm}",
+        "composite" : false
+      }, {
+        "id" : "c55cb35a-2602-47a6-a628-fc5a55341426",
+        "name" : "manage-users",
+        "description" : "${role_manage-users}",
+        "composite" : false
+      }, {
+        "id" : "4dc834d0-766b-45aa-ab3b-b7b976baa65d",
+        "name" : "realm-admin",
+        "description" : "${role_realm-admin}",
+        "composite" : true,
+        "composites" : {
+          "client" : {
+            "realm-management" : [ "view-users", "manage-identity-providers", "view-clients", "manage-realm", "manage-users", "impersonation", "view-realm", "view-events", "manage-clients", "manage-events", "view-identity-providers" ]
+          }
+        }
+      }, {
+        "id" : "d444a98f-ab5e-4857-9300-496e04e498f5",
+        "name" : "impersonation",
+        "description" : "${role_impersonation}",
+        "composite" : false
+      }, {
+        "id" : "2f6f1407-f334-434f-becf-771e3ebb5625",
+        "name" : "view-realm",
+        "description" : "${role_view-realm}",
+        "composite" : false
+      }, {
+        "id" : "a40d3211-5244-4d92-80c0-0d3215580250",
+        "name" : "manage-events",
+        "description" : "${role_manage-events}",
+        "composite" : false
+      }, {
+        "id" : "d11c407e-504f-4923-b243-e794afa0247e",
+        "name" : "view-events",
+        "description" : "${role_view-events}",
+        "composite" : false
+      }, {
+        "id" : "3ef6ace4-4e87-4c30-a8b3-1f0df25868c6",
+        "name" : "manage-clients",
+        "description" : "${role_manage-clients}",
+        "composite" : false
+      }, {
+        "id" : "fa2a4972-b8d0-452e-8e13-d2cf7eaac7aa",
+        "name" : "view-identity-providers",
+        "description" : "${role_view-identity-providers}",
+        "composite" : false
+      } ],
+      "security-admin-console" : [ ],
+      "broker" : [ {
+        "id" : "1bc5aeb4-1df1-4402-8195-e2a72f6dca30",
+        "name" : "read-token",
+        "description" : "${role_read-token}",
+        "composite" : false
+      } ],
+      "account" : [ {
+        "id" : "71b5b5ff-b372-41a1-a427-7883fa64a8c7",
+        "name" : "manage-account",
+        "description" : "${role_manage-account}",
+        "composite" : false
+      }, {
+        "id" : "04daa556-8aeb-43ba-99c6-b393ec2a32d4",
+        "name" : "view-profile",
+        "description" : "${role_view-profile}",
+        "composite" : false
+      } ]
+    }
+  },
+  "requiredCredentials" : [ "password" ],
+  "otpPolicyType" : "totp",
+  "otpPolicyAlgorithm" : "HmacSHA1",
+  "otpPolicyInitialCounter" : 0,
+  "otpPolicyDigits" : 6,
+  "otpPolicyLookAheadWindow" : 1,
+  "otpPolicyPeriod" : 30,
+  "clientScopeMappings" : {
+    "realm-management" : [ {
+      "client" : "security-admin-console",
+      "roles" : [ "realm-admin" ]
+    } ]
+  },
+  "clients" : [ {
+    "id" : "ba27336f-3f89-471d-98d2-b8856bd6dbf1",
+    "clientId" : "realm-management",
+    "name" : "${client_realm-management}",
+    "surrogateAuthRequired" : false,
+    "enabled" : true,
+    "clientAuthenticatorType" : "client-secret",
+    "secret" : "26aee4e9-8eec-421b-90a9-238538f5897a",
+    "redirectUris" : [ ],
+    "webOrigins" : [ ],
+    "notBefore" : 0,
+    "bearerOnly" : true,
+    "consentRequired" : false,
+    "serviceAccountsEnabled" : false,
+    "directGrantsOnly" : false,
+    "publicClient" : false,
+    "frontchannelLogout" : false,
+    "attributes" : { },
+    "fullScopeAllowed" : false,
+    "nodeReRegistrationTimeout" : 0,
+    "protocolMappers" : [ {
+      "id" : "5d56eba1-724e-4904-a8f8-86ca264a82cf",
+      "name" : "family name",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-property-mapper",
+      "consentRequired" : true,
+      "consentText" : "${familyName}",
+      "config" : {
+        "user.attribute" : "lastName",
+        "id.token.claim" : "true",
+        "access.token.claim" : "true",
+        "claim.name" : "family_name",
+        "Claim JSON Type" : "String"
+      }
+    }, {
+      "id" : "18a30786-89f9-4744-8f36-4de811a591ae",
+      "name" : "email",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-property-mapper",
+      "consentRequired" : true,
+      "consentText" : "${email}",
+      "config" : {
+        "user.attribute" : "email",
+        "id.token.claim" : "true",
+        "access.token.claim" : "true",
+        "claim.name" : "email",
+        "Claim JSON Type" : "String"
+      }
+    }, {
+      "id" : "4d1c4456-0c0d-49b9-bfba-c2c83645aeb2",
+      "name" : "username",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-property-mapper",
+      "consentRequired" : true,
+      "consentText" : "${username}",
+      "config" : {
+        "user.attribute" : "username",
+        "id.token.claim" : "true",
+        "access.token.claim" : "true",
+        "claim.name" : "preferred_username",
+        "Claim JSON Type" : "String"
+      }
+    }, {
+      "id" : "1a19db43-2346-4a24-b6f0-1b8d7fc1353e",
+      "name" : "role list",
+      "protocol" : "saml",
+      "protocolMapper" : "saml-role-list-mapper",
+      "consentRequired" : false,
+      "config" : {
+        "single" : "false",
+        "attribute.nameformat" : "Basic",
+        "attribute.name" : "Role"
+      }
+    }, {
+      "id" : "a18612f0-9eb3-4d81-af0c-b0749b83fbd3",
+      "name" : "given name",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-property-mapper",
+      "consentRequired" : true,
+      "consentText" : "${givenName}",
+      "config" : {
+        "user.attribute" : "firstName",
+        "id.token.claim" : "true",
+        "access.token.claim" : "true",
+        "claim.name" : "given_name",
+        "Claim JSON Type" : "String"
+      }
+    }, {
+      "id" : "70c26044-c7fc-4090-98e1-670fef006e25",
+      "name" : "full name",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-full-name-mapper",
+      "consentRequired" : true,
+      "consentText" : "${fullName}",
+      "config" : {
+        "id.token.claim" : "true",
+        "access.token.claim" : "true"
+      }
+    } ]
+  }, {
+    "id" : "3fdddd5e-0022-4f6d-8fdf-212266db7fd4",
+    "clientId" : "security-admin-console",
+    "name" : "${client_security-admin-console}",
+    "baseUrl" : "/auth/admin/Migration/console/index.html",
+    "surrogateAuthRequired" : false,
+    "enabled" : true,
+    "clientAuthenticatorType" : "client-secret",
+    "secret" : "38ce8135-738d-4103-85ac-c3470ac8824d",
+    "redirectUris" : [ "/auth/admin/Migration/console/*" ],
+    "webOrigins" : [ ],
+    "notBefore" : 0,
+    "bearerOnly" : false,
+    "consentRequired" : false,
+    "serviceAccountsEnabled" : false,
+    "directGrantsOnly" : false,
+    "publicClient" : true,
+    "frontchannelLogout" : false,
+    "attributes" : { },
+    "fullScopeAllowed" : false,
+    "nodeReRegistrationTimeout" : 0,
+    "protocolMappers" : [ {
+      "id" : "be58fe30-b767-4566-9192-a4fa81fafa2c",
+      "name" : "role list",
+      "protocol" : "saml",
+      "protocolMapper" : "saml-role-list-mapper",
+      "consentRequired" : false,
+      "config" : {
+        "single" : "false",
+        "attribute.nameformat" : "Basic",
+        "attribute.name" : "Role"
+      }
+    }, {
+      "id" : "c97523fd-fd4f-48d7-8937-bd434fa374fd",
+      "name" : "given name",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-property-mapper",
+      "consentRequired" : true,
+      "consentText" : "${givenName}",
+      "config" : {
+        "user.attribute" : "firstName",
+        "id.token.claim" : "true",
+        "access.token.claim" : "true",
+        "claim.name" : "given_name",
+        "Claim JSON Type" : "String"
+      }
+    }, {
+      "id" : "2323a85d-2686-46d4-bea8-e36524920f2e",
+      "name" : "email",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-property-mapper",
+      "consentRequired" : true,
+      "consentText" : "${email}",
+      "config" : {
+        "user.attribute" : "email",
+        "id.token.claim" : "true",
+        "access.token.claim" : "true",
+        "claim.name" : "email",
+        "Claim JSON Type" : "String"
+      }
+    }, {
+      "id" : "56f8a80e-9e99-4add-b918-b864ca3f6f5c",
+      "name" : "username",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-property-mapper",
+      "consentRequired" : true,
+      "consentText" : "${username}",
+      "config" : {
+        "user.attribute" : "username",
+        "id.token.claim" : "true",
+        "access.token.claim" : "true",
+        "claim.name" : "preferred_username",
+        "Claim JSON Type" : "String"
+      }
+    }, {
+      "id" : "f9f51e8d-d5af-456c-be5a-3019fb8c0910",
+      "name" : "family name",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-property-mapper",
+      "consentRequired" : true,
+      "consentText" : "${familyName}",
+      "config" : {
+        "user.attribute" : "lastName",
+        "id.token.claim" : "true",
+        "access.token.claim" : "true",
+        "claim.name" : "family_name",
+        "Claim JSON Type" : "String"
+      }
+    }, {
+      "id" : "3d8fbb0c-9058-4dde-b675-ca77a153ceb8",
+      "name" : "full name",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-full-name-mapper",
+      "consentRequired" : true,
+      "consentText" : "${fullName}",
+      "config" : {
+        "id.token.claim" : "true",
+        "access.token.claim" : "true"
+      }
+    } ]
+  }, {
+    "id" : "068fcf1a-7048-43df-b3dd-e6c484e8b051",
+    "clientId" : "broker",
+    "name" : "${client_broker}",
+    "surrogateAuthRequired" : false,
+    "enabled" : true,
+    "clientAuthenticatorType" : "client-secret",
+    "secret" : "041b50a6-54b5-4cff-84ef-1b7c388d3395",
+    "redirectUris" : [ ],
+    "webOrigins" : [ ],
+    "notBefore" : 0,
+    "bearerOnly" : false,
+    "consentRequired" : false,
+    "serviceAccountsEnabled" : false,
+    "directGrantsOnly" : false,
+    "publicClient" : false,
+    "frontchannelLogout" : false,
+    "attributes" : { },
+    "fullScopeAllowed" : false,
+    "nodeReRegistrationTimeout" : 0,
+    "protocolMappers" : [ {
+      "id" : "57c6f779-c96b-4f03-b268-354af2a8731e",
+      "name" : "email",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-property-mapper",
+      "consentRequired" : true,
+      "consentText" : "${email}",
+      "config" : {
+        "user.attribute" : "email",
+        "id.token.claim" : "true",
+        "access.token.claim" : "true",
+        "claim.name" : "email",
+        "Claim JSON Type" : "String"
+      }
+    }, {
+      "id" : "f17a7b9f-9363-44bd-8320-df36f22ca712",
+      "name" : "given name",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-property-mapper",
+      "consentRequired" : true,
+      "consentText" : "${givenName}",
+      "config" : {
+        "user.attribute" : "firstName",
+        "id.token.claim" : "true",
+        "access.token.claim" : "true",
+        "claim.name" : "given_name",
+        "Claim JSON Type" : "String"
+      }
+    }, {
+      "id" : "ec1bf022-9e98-4f29-9bf0-f0a49bd844ad",
+      "name" : "role list",
+      "protocol" : "saml",
+      "protocolMapper" : "saml-role-list-mapper",
+      "consentRequired" : false,
+      "config" : {
+        "single" : "false",
+        "attribute.nameformat" : "Basic",
+        "attribute.name" : "Role"
+      }
+    }, {
+      "id" : "0d54c616-326b-4fe7-bbfa-af9a28304dc5",
+      "name" : "username",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-property-mapper",
+      "consentRequired" : true,
+      "consentText" : "${username}",
+      "config" : {
+        "user.attribute" : "username",
+        "id.token.claim" : "true",
+        "access.token.claim" : "true",
+        "claim.name" : "preferred_username",
+        "Claim JSON Type" : "String"
+      }
+    }, {
+      "id" : "89ce95b9-a268-4306-a1ad-86066d0cdd03",
+      "name" : "family name",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-property-mapper",
+      "consentRequired" : true,
+      "consentText" : "${familyName}",
+      "config" : {
+        "user.attribute" : "lastName",
+        "id.token.claim" : "true",
+        "access.token.claim" : "true",
+        "claim.name" : "family_name",
+        "Claim JSON Type" : "String"
+      }
+    }, {
+      "id" : "8236274d-af69-4fc4-8804-a02d4af66157",
+      "name" : "full name",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-full-name-mapper",
+      "consentRequired" : true,
+      "consentText" : "${fullName}",
+      "config" : {
+        "id.token.claim" : "true",
+        "access.token.claim" : "true"
+      }
+    } ]
+  }, {
+    "id" : "717b9e58-87ed-402d-a8f8-a37fd5e7c951",
+    "clientId" : "account",
+    "name" : "${client_account}",
+    "baseUrl" : "/auth/realms/Migration/account",
+    "surrogateAuthRequired" : false,
+    "enabled" : true,
+    "clientAuthenticatorType" : "client-secret",
+    "secret" : "9ea62eb5-5478-454a-a479-4012f8967f9c",
+    "defaultRoles" : [ "view-profile", "manage-account" ],
+    "redirectUris" : [ "/auth/realms/Migration/account/*" ],
+    "webOrigins" : [ ],
+    "notBefore" : 0,
+    "bearerOnly" : false,
+    "consentRequired" : false,
+    "serviceAccountsEnabled" : false,
+    "directGrantsOnly" : false,
+    "publicClient" : false,
+    "frontchannelLogout" : false,
+    "attributes" : { },
+    "fullScopeAllowed" : false,
+    "nodeReRegistrationTimeout" : 0,
+    "protocolMappers" : [ {
+      "id" : "bfb4a165-2a55-4e4b-9b13-05e68822f5d6",
+      "name" : "family name",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-property-mapper",
+      "consentRequired" : true,
+      "consentText" : "${familyName}",
+      "config" : {
+        "user.attribute" : "lastName",
+        "id.token.claim" : "true",
+        "access.token.claim" : "true",
+        "claim.name" : "family_name",
+        "Claim JSON Type" : "String"
+      }
+    }, {
+      "id" : "c505e313-d478-4b1d-94df-c2c9b6036a95",
+      "name" : "role list",
+      "protocol" : "saml",
+      "protocolMapper" : "saml-role-list-mapper",
+      "consentRequired" : false,
+      "config" : {
+        "single" : "false",
+        "attribute.nameformat" : "Basic",
+        "attribute.name" : "Role"
+      }
+    }, {
+      "id" : "12fc43f0-19b7-4b4a-b50a-40b6fc344ede",
+      "name" : "email",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-property-mapper",
+      "consentRequired" : true,
+      "consentText" : "${email}",
+      "config" : {
+        "user.attribute" : "email",
+        "id.token.claim" : "true",
+        "access.token.claim" : "true",
+        "claim.name" : "email",
+        "Claim JSON Type" : "String"
+      }
+    }, {
+      "id" : "250f3bf3-2655-4482-a814-3adcc7cef5a4",
+      "name" : "full name",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-full-name-mapper",
+      "consentRequired" : true,
+      "consentText" : "${fullName}",
+      "config" : {
+        "id.token.claim" : "true",
+        "access.token.claim" : "true"
+      }
+    }, {
+      "id" : "6cf70d19-6a9e-4abf-8917-38b87bac15d6",
+      "name" : "username",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-property-mapper",
+      "consentRequired" : true,
+      "consentText" : "${username}",
+      "config" : {
+        "user.attribute" : "username",
+        "id.token.claim" : "true",
+        "access.token.claim" : "true",
+        "claim.name" : "preferred_username",
+        "Claim JSON Type" : "String"
+      }
+    }, {
+      "id" : "6cdeae21-ca97-4723-b880-d5aa35fa77b0",
+      "name" : "given name",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-property-mapper",
+      "consentRequired" : true,
+      "consentText" : "${givenName}",
+      "config" : {
+        "user.attribute" : "firstName",
+        "id.token.claim" : "true",
+        "access.token.claim" : "true",
+        "claim.name" : "given_name",
+        "Claim JSON Type" : "String"
+      }
+    } ]
+  } ],
+  "browserSecurityHeaders" : {
+    "contentSecurityPolicy" : "frame-src 'self'",
+    "xFrameOptions" : "SAMEORIGIN"
+  },
+  "smtpServer" : { },
+  "eventsEnabled" : false,
+  "eventsListeners" : [ "jboss-logging" ],
+  "enabledEventTypes" : [ ],
+  "adminEventsEnabled" : false,
+  "adminEventsDetailsEnabled" : false,
+  "identityFederationEnabled" : false,
+  "internationalizationEnabled" : false,
+  "supportedLocales" : [ ],
+  "authenticationFlows" : [ {
+    "alias" : "registration form",
+    "description" : "registration form",
+    "providerId" : "form-flow",
+    "topLevel" : false,
+    "builtIn" : true,
+    "authenticationExecutions" : [ {
+      "authenticator" : "registration-user-creation",
+      "autheticatorFlow" : false,
+      "requirement" : "REQUIRED",
+      "userSetupAllowed" : false,
+      "priority" : 20
+    }, {
+      "authenticator" : "registration-profile-action",
+      "autheticatorFlow" : false,
+      "requirement" : "REQUIRED",
+      "userSetupAllowed" : false,
+      "priority" : 40
+    }, {
+      "authenticator" : "registration-password-action",
+      "autheticatorFlow" : false,
+      "requirement" : "REQUIRED",
+      "userSetupAllowed" : false,
+      "priority" : 50
+    }, {
+      "authenticator" : "registration-recaptcha-action",
+      "autheticatorFlow" : false,
+      "requirement" : "DISABLED",
+      "userSetupAllowed" : false,
+      "priority" : 60
+    } ]
+  }, {
+    "alias" : "direct grant",
+    "description" : "OpenID Connect Resource Owner Grant",
+    "providerId" : "basic-flow",
+    "topLevel" : true,
+    "builtIn" : true,
+    "authenticationExecutions" : [ {
+      "authenticator" : "direct-grant-validate-username",
+      "autheticatorFlow" : false,
+      "requirement" : "REQUIRED",
+      "userSetupAllowed" : false,
+      "priority" : 10
+    }, {
+      "authenticator" : "direct-grant-validate-password",
+      "autheticatorFlow" : false,
+      "requirement" : "REQUIRED",
+      "userSetupAllowed" : false,
+      "priority" : 20
+    }, {
+      "authenticator" : "direct-grant-validate-otp",
+      "autheticatorFlow" : false,
+      "requirement" : "OPTIONAL",
+      "userSetupAllowed" : false,
+      "priority" : 30
+    } ]
+  }, {
+    "alias" : "reset credentials",
+    "description" : "Reset credentials for a user if they forgot their password or something",
+    "providerId" : "basic-flow",
+    "topLevel" : true,
+    "builtIn" : true,
+    "authenticationExecutions" : [ {
+      "authenticator" : "reset-credentials-choose-user",
+      "autheticatorFlow" : false,
+      "requirement" : "REQUIRED",
+      "userSetupAllowed" : false,
+      "priority" : 10
+    }, {
+      "authenticator" : "reset-credential-email",
+      "autheticatorFlow" : false,
+      "requirement" : "REQUIRED",
+      "userSetupAllowed" : false,
+      "priority" : 20
+    }, {
+      "authenticator" : "reset-password",
+      "autheticatorFlow" : false,
+      "requirement" : "REQUIRED",
+      "userSetupAllowed" : false,
+      "priority" : 30
+    }, {
+      "authenticator" : "reset-otp",
+      "autheticatorFlow" : false,
+      "requirement" : "OPTIONAL",
+      "userSetupAllowed" : false,
+      "priority" : 40
+    } ]
+  }, {
+    "alias" : "forms",
+    "description" : "Username, password, otp and other auth forms.",
+    "providerId" : "basic-flow",
+    "topLevel" : false,
+    "builtIn" : true,
+    "authenticationExecutions" : [ {
+      "authenticator" : "auth-username-password-form",
+      "autheticatorFlow" : false,
+      "requirement" : "REQUIRED",
+      "userSetupAllowed" : false,
+      "priority" : 10
+    }, {
+      "authenticator" : "auth-otp-form",
+      "autheticatorFlow" : false,
+      "requirement" : "OPTIONAL",
+      "userSetupAllowed" : false,
+      "priority" : 20
+    } ]
+  }, {
+    "alias" : "clients",
+    "description" : "Base authentication for clients",
+    "providerId" : "client-flow",
+    "topLevel" : true,
+    "builtIn" : true,
+    "authenticationExecutions" : [ {
+      "authenticator" : "client-secret",
+      "autheticatorFlow" : false,
+      "requirement" : "ALTERNATIVE",
+      "userSetupAllowed" : false,
+      "priority" : 10
+    }, {
+      "authenticator" : "client-jwt",
+      "autheticatorFlow" : false,
+      "requirement" : "ALTERNATIVE",
+      "userSetupAllowed" : false,
+      "priority" : 20
+    } ]
+  }, {
+    "alias" : "browser",
+    "description" : "browser based authentication",
+    "providerId" : "basic-flow",
+    "topLevel" : true,
+    "builtIn" : true,
+    "authenticationExecutions" : [ {
+      "authenticator" : "auth-cookie",
+      "autheticatorFlow" : false,
+      "requirement" : "ALTERNATIVE",
+      "userSetupAllowed" : false,
+      "priority" : 10
+    }, {
+      "authenticator" : "auth-spnego",
+      "autheticatorFlow" : false,
+      "requirement" : "DISABLED",
+      "userSetupAllowed" : false,
+      "priority" : 20
+    }, {
+      "flowAlias" : "forms",
+      "autheticatorFlow" : true,
+      "requirement" : "ALTERNATIVE",
+      "userSetupAllowed" : false,
+      "priority" : 30
+    } ]
+  }, {
+    "alias" : "registration",
+    "description" : "registration flow",
+    "providerId" : "basic-flow",
+    "topLevel" : true,
+    "builtIn" : true,
+    "authenticationExecutions" : [ {
+      "authenticator" : "registration-page-form",
+      "flowAlias" : "registration form",
+      "autheticatorFlow" : true,
+      "requirement" : "REQUIRED",
+      "userSetupAllowed" : false,
+      "priority" : 10
+    } ]
+  } ],
+  "authenticatorConfig" : [ ],
+  "requiredActions" : [ {
+    "alias" : "CONFIGURE_TOTP",
+    "name" : "Configure Totp",
+    "providerId" : "CONFIGURE_TOTP",
+    "enabled" : true,
+    "defaultAction" : false,
+    "config" : { }
+  }, {
+    "alias" : "VERIFY_EMAIL",
+    "name" : "Verify Email",
+    "providerId" : "VERIFY_EMAIL",
+    "enabled" : true,
+    "defaultAction" : false,
+    "config" : { }
+  }, {
+    "alias" : "terms_and_conditions",
+    "name" : "Terms and Conditions",
+    "providerId" : "terms_and_conditions",
+    "enabled" : false,
+    "defaultAction" : false,
+    "config" : { }
+  }, {
+    "alias" : "UPDATE_PASSWORD",
+    "name" : "Update Password",
+    "providerId" : "UPDATE_PASSWORD",
+    "enabled" : true,
+    "defaultAction" : false,
+    "config" : { }
+  }, {
+    "alias" : "UPDATE_PROFILE",
+    "name" : "Update Profile",
+    "providerId" : "UPDATE_PROFILE",
+    "enabled" : true,
+    "defaultAction" : false,
+    "config" : { }
+  } ],
+  "browserFlow" : "browser",
+  "registrationFlow" : "registration",
+  "directGrantFlow" : "direct grant",
+  "resetCredentialsFlow" : "reset credentials",
+  "clientAuthenticationFlow" : "clients"
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-16.json b/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-16.json
new file mode 100644
index 0000000..3719d04
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-16.json
@@ -0,0 +1,796 @@
+{
+  "id" : "Migration",
+  "realm" : "Migration",
+  "notBefore" : 0,
+  "revokeRefreshToken" : false,
+  "accessTokenLifespan" : 300,
+  "ssoSessionIdleTimeout" : 1800,
+  "ssoSessionMaxLifespan" : 36000,
+  "offlineSessionIdleTimeout" : 2592000,
+  "accessCodeLifespan" : 60,
+  "accessCodeLifespanUserAction" : 300,
+  "accessCodeLifespanLogin" : 1800,
+  "enabled" : true,
+  "sslRequired" : "external",
+  "registrationAllowed" : false,
+  "registrationEmailAsUsername" : false,
+  "rememberMe" : false,
+  "verifyEmail" : false,
+  "resetPasswordAllowed" : false,
+  "editUsernameAllowed" : false,
+  "bruteForceProtected" : false,
+  "maxFailureWaitSeconds" : 900,
+  "minimumQuickLoginWaitSeconds" : 60,
+  "waitIncrementSeconds" : 60,
+  "quickLoginCheckMilliSeconds" : 1000,
+  "maxDeltaTimeSeconds" : 43200,
+  "failureFactor" : 30,
+  "privateKey" : "MIIEowIBAAKCAQEAg/XlZqOYbYHyzHjWKwCD35JKloSyBBaIQgQbUjmWSBLw6xyNLSSvI45lmhoxcJTjxeZ+LKudfcoDVcLah1kYmTiS5YtNROeqmdWTkekdsAW8PYQJ0ScpatJ3jQ6xpe2E/AQWel5h6HI07O/r1mc3JDCXSe5zKdV9C0aGZpQSU2jWkVmP1cc2EZg5bVD1v057CUpKAX3qkloXpRedq6tMgwRSurhgnWDt93xWcv/+zz1Rw400batHmAm8Xa792jfZhSjvxtv9Q83Eb9jqi+c3BnxC3hucUDc6ivm8UgKYha054IOVbG8wDtmhZF8LlvLpjPk9iHuygS0zRnRuCo1+pQIDAQABAoIBACx5B8oSooFthS2CH/O4JbmIbRjTOceE7IELL0YD4HED6SvjoHSxY1EhYX6RC05871K3/pgBcn99QKh7lfh9f3vMBD3WN8FcLjPQNf67yOSU2j8FK+XQQ/YbXm0soZRhOytQGV8+RdL4AnxD04CboorQ0Xv6H9feelj9eLhDePWg5qEGHZJA6zGYiOUBALAL+SXoL59LWWLEM48TQWM0yGCA3mQM0iWCclbLNM1ls5gwMxSDdJeKC/3qlB6egGqPtXCEJdQXYqt3do8UUnxdQEkRGlJx14cSoH7fmZyZjLsEBcQT5uoAHI7/NMVN1DoFgwMTsq/MAATh3ngHqSl6J2ECgYEA3Ati3EaI0Vb8KzdjwZVgk9/KKnGLcswOl+cfU+lL9Vv5W4lVht3zvNAO8mUjtTSpjCF7LlY2lD9JEsv4cA9T3v2L7ZjkBiD6S/YYnEZYGAOjJb+LniRLqSVgN3beUgSiG/zzwuJm92J3dIcqMIPi4gdLMJ7KAv9qgbOddAy4b3kCgYEAmYXlqAu+Vgyjj3wzfgeXKzYkUfbEXUzsdugpW2gCvKi2/lJwmzORfsvSL88CxKZvzbEb1AJOrW+aPUbO0vPWo07ztphPKed+Gydp2G837f24eGZpBpxG6ATIa5MjtCHgX5guTMA3sJCFyLdMacRmlkmZEwbk7e9QxCp9HOmFoY0CgYB5u1LVya+nIBghUGM/lQP4yrVtBaO/vmPUZWhPY6FB+7/XhAJsuh09N10NfCZk+N1TSLJ8z/UhzmD+pRir5c7gbiQbLZn4SgYuP9cdnUze/CQlnfH/atTwnly8UmZruWR1V1sDVXzhHvg23w/YBx5dLOvL2gyn2+VwG43fxanDAQKBgFpFqWzOuvTOKb7NQVnyDKmUBHdNqtlRyhmBGhBtcG6OpkuVHjGkeQEdyFHkX7RSSZuhcMORN8IzxXYSlLrmNmeAnT3ZAXOac0R0QIDLpQ+ECVyCm28PpYH4jgDzXCMnaE/NpCvtOtHPqVsErSHkIo5saF4Px71A4zT15uuBRNphAoGBAMXV0eqeZOr/iHGIie3Ol0atZuB9b/BgLJBTFsCbFoVLdMAah4i2MXDm3vOUWlPf2VFL1LcKXYQ1GZ79We5LqG5w1CLA5WNt93U3yyl3/V5w02My5dVhz9BD4kWhZcvih+uVuHBxeI8Q8AvU63qqT4punQW4SSAHC+9e3U62aNc+",
+  "publicKey" : "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAg/XlZqOYbYHyzHjWKwCD35JKloSyBBaIQgQbUjmWSBLw6xyNLSSvI45lmhoxcJTjxeZ+LKudfcoDVcLah1kYmTiS5YtNROeqmdWTkekdsAW8PYQJ0ScpatJ3jQ6xpe2E/AQWel5h6HI07O/r1mc3JDCXSe5zKdV9C0aGZpQSU2jWkVmP1cc2EZg5bVD1v057CUpKAX3qkloXpRedq6tMgwRSurhgnWDt93xWcv/+zz1Rw400batHmAm8Xa792jfZhSjvxtv9Q83Eb9jqi+c3BnxC3hucUDc6ivm8UgKYha054IOVbG8wDtmhZF8LlvLpjPk9iHuygS0zRnRuCo1+pQIDAQAB",
+  "certificate" : "MIICoTCCAYkCBgFQs9TiPDANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAlNaWdyYXRpb24wHhcNMTUxMDI5MTMzOTQ0WhcNMjUxMDI5MTM0MTI0WjAUMRIwEAYDVQQDDAlNaWdyYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCD9eVmo5htgfLMeNYrAIPfkkqWhLIEFohCBBtSOZZIEvDrHI0tJK8jjmWaGjFwlOPF5n4sq519ygNVwtqHWRiZOJLli01E56qZ1ZOR6R2wBbw9hAnRJylq0neNDrGl7YT8BBZ6XmHocjTs7+vWZzckMJdJ7nMp1X0LRoZmlBJTaNaRWY/VxzYRmDltUPW/TnsJSkoBfeqSWhelF52rq0yDBFK6uGCdYO33fFZy//7PPVHDjTRtq0eYCbxdrv3aN9mFKO/G2/1DzcRv2OqL5zcGfELeG5xQNzqK+bxSApiFrTngg5VsbzAO2aFkXwuW8umM+T2Ie7KBLTNGdG4KjX6lAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAIOonQw/DIwSDU2oFyoaZ7VfFcT6Uh3QXfLB4ZYDek4m6Y8onrtqzuGo2Z2tcokV9PPkBAQtefSxIqx4COC9OeDBy1DeY04AkpprywLuOzCL/OBCV9QwiMkuBYYC5JJCIyTuo3qYxkuDA/iwDdH7vNDGohE6TDYmrC3Yp3lsLaHZ1lG52Df3qL8wo3/PdwzCOmyRGfm3vscTY+PqVCYcfxg9nPkoU3FwUJDAshjMxRw7/aafG8OmQ2EeYH4HV/FsULFrwoVF0Up8TkqAhmdJJ+vaGqaHHXQyiIPTMCdrkE58jAvtpbX0XCEDiNXT9qORhmjKP8Xq13XOQTfI5DRo8Qc=",
+  "codeSecret" : "56227bc5-edb9-419c-a019-d61a7c6ffb74",
+  "roles" : {
+    "realm" : [ {
+      "id" : "c4aae789-de76-4130-a06b-a28113ada698",
+      "name" : "offline_access",
+      "description" : "${role_offline-access}",
+      "scopeParamRequired" : true,
+      "composite" : false
+    } ],
+    "client" : {
+      "realm-management" : [ {
+        "id" : "22345bd8-afee-44c3-9958-a134e729aaa7",
+        "name" : "view-identity-providers",
+        "description" : "${role_view-identity-providers}",
+        "scopeParamRequired" : false,
+        "composite" : false
+      }, {
+        "id" : "6c6bb910-a769-4e92-b009-db4b9ab32c67",
+        "name" : "manage-events",
+        "description" : "${role_manage-events}",
+        "scopeParamRequired" : false,
+        "composite" : false
+      }, {
+        "id" : "5327bf34-5a16-4f36-bb15-100a25aac33e",
+        "name" : "view-realm",
+        "description" : "${role_view-realm}",
+        "scopeParamRequired" : false,
+        "composite" : false
+      }, {
+        "id" : "3c52d428-e3e5-40b3-92d4-ab6195b7dce5",
+        "name" : "create-client",
+        "description" : "${role_create-client}",
+        "scopeParamRequired" : false,
+        "composite" : false
+      }, {
+        "id" : "9999e081-5321-4c19-a8ac-27cea3bbde3a",
+        "name" : "impersonation",
+        "description" : "${role_impersonation}",
+        "scopeParamRequired" : false,
+        "composite" : false
+      }, {
+        "id" : "7c857cf1-b66e-4935-8749-580062d4719a",
+        "name" : "manage-identity-providers",
+        "description" : "${role_manage-identity-providers}",
+        "scopeParamRequired" : false,
+        "composite" : false
+      }, {
+        "id" : "549d2e65-d347-4221-bde0-65fff6580fc2",
+        "name" : "view-events",
+        "description" : "${role_view-events}",
+        "scopeParamRequired" : false,
+        "composite" : false
+      }, {
+        "id" : "aa1676b8-a92a-4c99-b266-54858129942d",
+        "name" : "view-users",
+        "description" : "${role_view-users}",
+        "scopeParamRequired" : false,
+        "composite" : false
+      }, {
+        "id" : "6c9a78fa-0e37-48bf-a9b5-2062312b0f33",
+        "name" : "manage-clients",
+        "description" : "${role_manage-clients}",
+        "scopeParamRequired" : false,
+        "composite" : false
+      }, {
+        "id" : "d38072d6-66fe-4102-8d4d-b5e8e2721e43",
+        "name" : "manage-realm",
+        "description" : "${role_manage-realm}",
+        "scopeParamRequired" : false,
+        "composite" : false
+      }, {
+        "id" : "a85da016-830e-42dd-8318-3cc8c28d3382",
+        "name" : "manage-users",
+        "description" : "${role_manage-users}",
+        "scopeParamRequired" : false,
+        "composite" : false
+      }, {
+        "id" : "0ab22444-1235-4391-ac10-571b33065177",
+        "name" : "realm-admin",
+        "description" : "${role_realm-admin}",
+        "scopeParamRequired" : false,
+        "composite" : true,
+        "composites" : {
+          "client" : {
+            "realm-management" : [ "view-identity-providers", "manage-clients", "manage-events", "view-realm", "manage-realm", "manage-users", "create-client", "impersonation", "view-events", "manage-identity-providers", "view-clients", "view-users" ]
+          }
+        }
+      }, {
+        "id" : "442fcc9e-46af-495a-9cdf-64d32dabc808",
+        "name" : "view-clients",
+        "description" : "${role_view-clients}",
+        "scopeParamRequired" : false,
+        "composite" : false
+      } ],
+      "security-admin-console" : [ ],
+      "broker" : [ {
+        "id" : "8d46836e-eb6c-4cf5-97fe-8b1b24a69e10",
+        "name" : "read-token",
+        "description" : "${role_read-token}",
+        "scopeParamRequired" : false,
+        "composite" : false
+      } ],
+      "account" : [ {
+        "id" : "40799d46-6574-4d45-a157-33cc15e3e2f1",
+        "name" : "manage-account",
+        "description" : "${role_manage-account}",
+        "scopeParamRequired" : false,
+        "composite" : false
+      }, {
+        "id" : "d6056197-e9a3-4922-8b1b-ce6e99a71a43",
+        "name" : "view-profile",
+        "description" : "${role_view-profile}",
+        "scopeParamRequired" : false,
+        "composite" : false
+      } ]
+    }
+  },
+  "defaultRoles" : [ "offline_access" ],
+  "requiredCredentials" : [ "password" ],
+  "otpPolicyType" : "totp",
+  "otpPolicyAlgorithm" : "HmacSHA1",
+  "otpPolicyInitialCounter" : 0,
+  "otpPolicyDigits" : 6,
+  "otpPolicyLookAheadWindow" : 1,
+  "otpPolicyPeriod" : 30,
+  "clientScopeMappings" : {
+    "realm-management" : [ {
+      "client" : "security-admin-console",
+      "roles" : [ "realm-admin" ]
+    } ]
+  },
+  "clients" : [ {
+    "id" : "cdf6e789-79b9-41ad-b4a3-f02abd2aeab6",
+    "clientId" : "realm-management",
+    "name" : "${client_realm-management}",
+    "surrogateAuthRequired" : false,
+    "enabled" : true,
+    "clientAuthenticatorType" : "client-secret",
+    "secret" : "c51e802e-e33b-431e-8e74-c2ebd4ba6abf",
+    "redirectUris" : [ ],
+    "webOrigins" : [ ],
+    "notBefore" : 0,
+    "bearerOnly" : true,
+    "consentRequired" : false,
+    "serviceAccountsEnabled" : false,
+    "directGrantsOnly" : false,
+    "publicClient" : false,
+    "frontchannelLogout" : false,
+    "attributes" : { },
+    "fullScopeAllowed" : false,
+    "nodeReRegistrationTimeout" : 0,
+    "protocolMappers" : [ {
+      "id" : "cfaff5c8-a0e3-42af-8dcd-f7ae6000a240",
+      "name" : "email",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-property-mapper",
+      "consentRequired" : true,
+      "consentText" : "${email}",
+      "config" : {
+        "user.attribute" : "email",
+        "id.token.claim" : "true",
+        "access.token.claim" : "true",
+        "claim.name" : "email",
+        "jsonType.label" : "String"
+      }
+    }, {
+      "id" : "5a68a544-0373-4cf3-9978-aed944df478f",
+      "name" : "full name",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-full-name-mapper",
+      "consentRequired" : true,
+      "consentText" : "${fullName}",
+      "config" : {
+        "id.token.claim" : "true",
+        "access.token.claim" : "true"
+      }
+    }, {
+      "id" : "41c006db-88d6-42a6-addd-8efb535f1a7d",
+      "name" : "role list",
+      "protocol" : "saml",
+      "protocolMapper" : "saml-role-list-mapper",
+      "consentRequired" : false,
+      "config" : {
+        "single" : "false",
+        "attribute.nameformat" : "Basic",
+        "attribute.name" : "Role"
+      }
+    }, {
+      "id" : "d6fd0e72-aa1e-417d-b28b-ec31946dc6fd",
+      "name" : "username",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-property-mapper",
+      "consentRequired" : true,
+      "consentText" : "${username}",
+      "config" : {
+        "user.attribute" : "username",
+        "id.token.claim" : "true",
+        "access.token.claim" : "true",
+        "claim.name" : "preferred_username",
+        "jsonType.label" : "String"
+      }
+    }, {
+      "id" : "d8692a7a-366d-407d-abc1-a6f45742c47c",
+      "name" : "given name",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-property-mapper",
+      "consentRequired" : true,
+      "consentText" : "${givenName}",
+      "config" : {
+        "user.attribute" : "firstName",
+        "id.token.claim" : "true",
+        "access.token.claim" : "true",
+        "claim.name" : "given_name",
+        "jsonType.label" : "String"
+      }
+    }, {
+      "id" : "54f4844b-aaa5-4260-b2aa-5dc446c8b978",
+      "name" : "family name",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-property-mapper",
+      "consentRequired" : true,
+      "consentText" : "${familyName}",
+      "config" : {
+        "user.attribute" : "lastName",
+        "id.token.claim" : "true",
+        "access.token.claim" : "true",
+        "claim.name" : "family_name",
+        "jsonType.label" : "String"
+      }
+    } ]
+  }, {
+    "id" : "7776fa56-ab87-4638-b42b-cc9537ab2fc2",
+    "clientId" : "security-admin-console",
+    "name" : "${client_security-admin-console}",
+    "baseUrl" : "/auth/admin/Migration/console/index.html",
+    "surrogateAuthRequired" : false,
+    "enabled" : true,
+    "clientAuthenticatorType" : "client-secret",
+    "secret" : "5e0673fa-921d-4415-9d92-3a4197d87e46",
+    "redirectUris" : [ "/auth/admin/Migration/console/*" ],
+    "webOrigins" : [ ],
+    "notBefore" : 0,
+    "bearerOnly" : false,
+    "consentRequired" : false,
+    "serviceAccountsEnabled" : false,
+    "directGrantsOnly" : false,
+    "publicClient" : true,
+    "frontchannelLogout" : false,
+    "attributes" : { },
+    "fullScopeAllowed" : false,
+    "nodeReRegistrationTimeout" : 0,
+    "protocolMappers" : [ {
+      "id" : "9ed45252-c571-44fe-ac5f-b30cea378ff1",
+      "name" : "locale",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-attribute-mapper",
+      "consentRequired" : false,
+      "consentText" : "${locale}",
+      "config" : {
+        "user.attribute" : "locale",
+        "id.token.claim" : "true",
+        "access.token.claim" : "true",
+        "claim.name" : "locale",
+        "jsonType.label" : "String"
+      }
+    }, {
+      "id" : "d1b5694e-e9e2-4d56-9019-bc658cdcded8",
+      "name" : "email",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-property-mapper",
+      "consentRequired" : true,
+      "consentText" : "${email}",
+      "config" : {
+        "user.attribute" : "email",
+        "id.token.claim" : "true",
+        "access.token.claim" : "true",
+        "claim.name" : "email",
+        "jsonType.label" : "String"
+      }
+    }, {
+      "id" : "497fee7a-23b4-4345-a872-63444a8b1a27",
+      "name" : "full name",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-full-name-mapper",
+      "consentRequired" : true,
+      "consentText" : "${fullName}",
+      "config" : {
+        "id.token.claim" : "true",
+        "access.token.claim" : "true"
+      }
+    }, {
+      "id" : "57881d46-deca-421e-a4c5-e023e747f68e",
+      "name" : "username",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-property-mapper",
+      "consentRequired" : true,
+      "consentText" : "${username}",
+      "config" : {
+        "user.attribute" : "username",
+        "id.token.claim" : "true",
+        "access.token.claim" : "true",
+        "claim.name" : "preferred_username",
+        "jsonType.label" : "String"
+      }
+    }, {
+      "id" : "9cfe7043-ba2b-49e2-8a1b-f1b23fcb5eb5",
+      "name" : "family name",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-property-mapper",
+      "consentRequired" : true,
+      "consentText" : "${familyName}",
+      "config" : {
+        "user.attribute" : "lastName",
+        "id.token.claim" : "true",
+        "access.token.claim" : "true",
+        "claim.name" : "family_name",
+        "jsonType.label" : "String"
+      }
+    }, {
+      "id" : "c37a3c4a-8999-4111-ae2a-98954a5a8674",
+      "name" : "role list",
+      "protocol" : "saml",
+      "protocolMapper" : "saml-role-list-mapper",
+      "consentRequired" : false,
+      "config" : {
+        "single" : "false",
+        "attribute.nameformat" : "Basic",
+        "attribute.name" : "Role"
+      }
+    }, {
+      "id" : "a7917c74-f18a-43a0-a787-7afc7b45a247",
+      "name" : "given name",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-property-mapper",
+      "consentRequired" : true,
+      "consentText" : "${givenName}",
+      "config" : {
+        "user.attribute" : "firstName",
+        "id.token.claim" : "true",
+        "access.token.claim" : "true",
+        "claim.name" : "given_name",
+        "jsonType.label" : "String"
+      }
+    } ]
+  }, {
+    "id" : "e7faae41-f5e8-4571-b280-5bbe0d5bcb12",
+    "clientId" : "broker",
+    "name" : "${client_broker}",
+    "surrogateAuthRequired" : false,
+    "enabled" : true,
+    "clientAuthenticatorType" : "client-secret",
+    "secret" : "b2a1f1ff-5157-4240-9354-69a6deb13ccb",
+    "redirectUris" : [ ],
+    "webOrigins" : [ ],
+    "notBefore" : 0,
+    "bearerOnly" : false,
+    "consentRequired" : false,
+    "serviceAccountsEnabled" : false,
+    "directGrantsOnly" : false,
+    "publicClient" : false,
+    "frontchannelLogout" : false,
+    "attributes" : { },
+    "fullScopeAllowed" : false,
+    "nodeReRegistrationTimeout" : 0,
+    "protocolMappers" : [ {
+      "id" : "b843f1fd-da0a-4d49-b367-3fb39f11383b",
+      "name" : "family name",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-property-mapper",
+      "consentRequired" : true,
+      "consentText" : "${familyName}",
+      "config" : {
+        "user.attribute" : "lastName",
+        "id.token.claim" : "true",
+        "access.token.claim" : "true",
+        "claim.name" : "family_name",
+        "jsonType.label" : "String"
+      }
+    }, {
+      "id" : "3b18c534-1e0a-474c-adf8-e9fbc33c05e8",
+      "name" : "full name",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-full-name-mapper",
+      "consentRequired" : true,
+      "consentText" : "${fullName}",
+      "config" : {
+        "id.token.claim" : "true",
+        "access.token.claim" : "true"
+      }
+    }, {
+      "id" : "b7e9db64-52f6-4aba-9437-deefab06abee",
+      "name" : "role list",
+      "protocol" : "saml",
+      "protocolMapper" : "saml-role-list-mapper",
+      "consentRequired" : false,
+      "config" : {
+        "single" : "false",
+        "attribute.nameformat" : "Basic",
+        "attribute.name" : "Role"
+      }
+    }, {
+      "id" : "2da52efa-e9d9-4b68-a296-0310059b7df2",
+      "name" : "username",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-property-mapper",
+      "consentRequired" : true,
+      "consentText" : "${username}",
+      "config" : {
+        "user.attribute" : "username",
+        "id.token.claim" : "true",
+        "access.token.claim" : "true",
+        "claim.name" : "preferred_username",
+        "jsonType.label" : "String"
+      }
+    }, {
+      "id" : "2d2df25d-26d1-4e7c-a85a-c485ab2cc0fe",
+      "name" : "given name",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-property-mapper",
+      "consentRequired" : true,
+      "consentText" : "${givenName}",
+      "config" : {
+        "user.attribute" : "firstName",
+        "id.token.claim" : "true",
+        "access.token.claim" : "true",
+        "claim.name" : "given_name",
+        "jsonType.label" : "String"
+      }
+    }, {
+      "id" : "d096910d-13ac-43a7-bad8-4d1bbfd34171",
+      "name" : "email",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-property-mapper",
+      "consentRequired" : true,
+      "consentText" : "${email}",
+      "config" : {
+        "user.attribute" : "email",
+        "id.token.claim" : "true",
+        "access.token.claim" : "true",
+        "claim.name" : "email",
+        "jsonType.label" : "String"
+      }
+    } ]
+  }, {
+    "id" : "a2864762-7cc1-4784-a540-439e611f29ba",
+    "clientId" : "account",
+    "name" : "${client_account}",
+    "baseUrl" : "/auth/realms/Migration/account",
+    "surrogateAuthRequired" : false,
+    "enabled" : true,
+    "clientAuthenticatorType" : "client-secret",
+    "secret" : "68cbd7a8-3b48-4751-a396-df7ab39a2fdf",
+    "defaultRoles" : [ "view-profile", "manage-account" ],
+    "redirectUris" : [ "/auth/realms/Migration/account/*" ],
+    "webOrigins" : [ ],
+    "notBefore" : 0,
+    "bearerOnly" : false,
+    "consentRequired" : false,
+    "serviceAccountsEnabled" : false,
+    "directGrantsOnly" : false,
+    "publicClient" : false,
+    "frontchannelLogout" : false,
+    "attributes" : { },
+    "fullScopeAllowed" : false,
+    "nodeReRegistrationTimeout" : 0,
+    "protocolMappers" : [ {
+      "id" : "ed2c87d0-299a-40ac-a11c-df7af41bb365",
+      "name" : "email",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-property-mapper",
+      "consentRequired" : true,
+      "consentText" : "${email}",
+      "config" : {
+        "user.attribute" : "email",
+        "id.token.claim" : "true",
+        "access.token.claim" : "true",
+        "claim.name" : "email",
+        "jsonType.label" : "String"
+      }
+    }, {
+      "id" : "80bc8d1f-3cb8-4362-890c-68d1a5c7263d",
+      "name" : "given name",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-property-mapper",
+      "consentRequired" : true,
+      "consentText" : "${givenName}",
+      "config" : {
+        "user.attribute" : "firstName",
+        "id.token.claim" : "true",
+        "access.token.claim" : "true",
+        "claim.name" : "given_name",
+        "jsonType.label" : "String"
+      }
+    }, {
+      "id" : "af93478f-176d-4be4-be5d-78a65dd88717",
+      "name" : "username",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-property-mapper",
+      "consentRequired" : true,
+      "consentText" : "${username}",
+      "config" : {
+        "user.attribute" : "username",
+        "id.token.claim" : "true",
+        "access.token.claim" : "true",
+        "claim.name" : "preferred_username",
+        "jsonType.label" : "String"
+      }
+    }, {
+      "id" : "b6c1704d-39fc-4b63-8f70-74561849654f",
+      "name" : "family name",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-property-mapper",
+      "consentRequired" : true,
+      "consentText" : "${familyName}",
+      "config" : {
+        "user.attribute" : "lastName",
+        "id.token.claim" : "true",
+        "access.token.claim" : "true",
+        "claim.name" : "family_name",
+        "jsonType.label" : "String"
+      }
+    }, {
+      "id" : "928dbc26-41a1-4342-ba92-c230a85e830c",
+      "name" : "role list",
+      "protocol" : "saml",
+      "protocolMapper" : "saml-role-list-mapper",
+      "consentRequired" : false,
+      "config" : {
+        "single" : "false",
+        "attribute.nameformat" : "Basic",
+        "attribute.name" : "Role"
+      }
+    }, {
+      "id" : "03a967ab-ed2b-402f-ae2f-10729084376c",
+      "name" : "full name",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-full-name-mapper",
+      "consentRequired" : true,
+      "consentText" : "${fullName}",
+      "config" : {
+        "id.token.claim" : "true",
+        "access.token.claim" : "true"
+      }
+    } ]
+  } ],
+  "browserSecurityHeaders" : {
+    "contentSecurityPolicy" : "frame-src 'self'",
+    "xFrameOptions" : "SAMEORIGIN"
+  },
+  "smtpServer" : { },
+  "eventsEnabled" : false,
+  "eventsListeners" : [ "jboss-logging" ],
+  "enabledEventTypes" : [ ],
+  "adminEventsEnabled" : false,
+  "adminEventsDetailsEnabled" : false,
+  "identityFederationEnabled" : false,
+  "internationalizationEnabled" : false,
+  "supportedLocales" : [ ],
+  "authenticationFlows" : [ {
+    "alias" : "reset credentials",
+    "description" : "Reset credentials for a user if they forgot their password or something",
+    "providerId" : "basic-flow",
+    "topLevel" : true,
+    "builtIn" : true,
+    "authenticationExecutions" : [ {
+      "authenticator" : "reset-credentials-choose-user",
+      "autheticatorFlow" : false,
+      "requirement" : "REQUIRED",
+      "userSetupAllowed" : false,
+      "priority" : 10
+    }, {
+      "authenticator" : "reset-credential-email",
+      "autheticatorFlow" : false,
+      "requirement" : "REQUIRED",
+      "userSetupAllowed" : false,
+      "priority" : 20
+    }, {
+      "authenticator" : "reset-password",
+      "autheticatorFlow" : false,
+      "requirement" : "REQUIRED",
+      "userSetupAllowed" : false,
+      "priority" : 30
+    }, {
+      "authenticator" : "reset-otp",
+      "autheticatorFlow" : false,
+      "requirement" : "OPTIONAL",
+      "userSetupAllowed" : false,
+      "priority" : 40
+    } ]
+  }, {
+    "alias" : "clients",
+    "description" : "Base authentication for clients",
+    "providerId" : "client-flow",
+    "topLevel" : true,
+    "builtIn" : true,
+    "authenticationExecutions" : [ {
+      "authenticator" : "client-secret",
+      "autheticatorFlow" : false,
+      "requirement" : "ALTERNATIVE",
+      "userSetupAllowed" : false,
+      "priority" : 10
+    }, {
+      "authenticator" : "client-jwt",
+      "autheticatorFlow" : false,
+      "requirement" : "ALTERNATIVE",
+      "userSetupAllowed" : false,
+      "priority" : 20
+    } ]
+  }, {
+    "alias" : "registration form",
+    "description" : "registration form",
+    "providerId" : "form-flow",
+    "topLevel" : false,
+    "builtIn" : true,
+    "authenticationExecutions" : [ {
+      "authenticator" : "registration-user-creation",
+      "autheticatorFlow" : false,
+      "requirement" : "REQUIRED",
+      "userSetupAllowed" : false,
+      "priority" : 20
+    }, {
+      "authenticator" : "registration-profile-action",
+      "autheticatorFlow" : false,
+      "requirement" : "REQUIRED",
+      "userSetupAllowed" : false,
+      "priority" : 40
+    }, {
+      "authenticator" : "registration-password-action",
+      "autheticatorFlow" : false,
+      "requirement" : "REQUIRED",
+      "userSetupAllowed" : false,
+      "priority" : 50
+    }, {
+      "authenticator" : "registration-recaptcha-action",
+      "autheticatorFlow" : false,
+      "requirement" : "DISABLED",
+      "userSetupAllowed" : false,
+      "priority" : 60
+    } ]
+  }, {
+    "alias" : "direct grant",
+    "description" : "OpenID Connect Resource Owner Grant",
+    "providerId" : "basic-flow",
+    "topLevel" : true,
+    "builtIn" : true,
+    "authenticationExecutions" : [ {
+      "authenticator" : "direct-grant-validate-username",
+      "autheticatorFlow" : false,
+      "requirement" : "REQUIRED",
+      "userSetupAllowed" : false,
+      "priority" : 10
+    }, {
+      "authenticator" : "direct-grant-validate-password",
+      "autheticatorFlow" : false,
+      "requirement" : "REQUIRED",
+      "userSetupAllowed" : false,
+      "priority" : 20
+    }, {
+      "authenticator" : "direct-grant-validate-otp",
+      "autheticatorFlow" : false,
+      "requirement" : "OPTIONAL",
+      "userSetupAllowed" : false,
+      "priority" : 30
+    } ]
+  }, {
+    "alias" : "registration",
+    "description" : "registration flow",
+    "providerId" : "basic-flow",
+    "topLevel" : true,
+    "builtIn" : true,
+    "authenticationExecutions" : [ {
+      "authenticator" : "registration-page-form",
+      "flowAlias" : "registration form",
+      "autheticatorFlow" : true,
+      "requirement" : "REQUIRED",
+      "userSetupAllowed" : false,
+      "priority" : 10
+    } ]
+  }, {
+    "alias" : "browser",
+    "description" : "browser based authentication",
+    "providerId" : "basic-flow",
+    "topLevel" : true,
+    "builtIn" : true,
+    "authenticationExecutions" : [ {
+      "authenticator" : "auth-cookie",
+      "autheticatorFlow" : false,
+      "requirement" : "ALTERNATIVE",
+      "userSetupAllowed" : false,
+      "priority" : 10
+    }, {
+      "authenticator" : "auth-spnego",
+      "autheticatorFlow" : false,
+      "requirement" : "DISABLED",
+      "userSetupAllowed" : false,
+      "priority" : 20
+    }, {
+      "flowAlias" : "forms",
+      "autheticatorFlow" : true,
+      "requirement" : "ALTERNATIVE",
+      "userSetupAllowed" : false,
+      "priority" : 30
+    } ]
+  }, {
+    "alias" : "forms",
+    "description" : "Username, password, otp and other auth forms.",
+    "providerId" : "basic-flow",
+    "topLevel" : false,
+    "builtIn" : true,
+    "authenticationExecutions" : [ {
+      "authenticator" : "auth-username-password-form",
+      "autheticatorFlow" : false,
+      "requirement" : "REQUIRED",
+      "userSetupAllowed" : false,
+      "priority" : 10
+    }, {
+      "authenticator" : "auth-otp-form",
+      "autheticatorFlow" : false,
+      "requirement" : "OPTIONAL",
+      "userSetupAllowed" : false,
+      "priority" : 20
+    } ]
+  } ],
+  "authenticatorConfig" : [ ],
+  "requiredActions" : [ {
+    "alias" : "terms_and_conditions",
+    "name" : "Terms and Conditions",
+    "providerId" : "terms_and_conditions",
+    "enabled" : false,
+    "defaultAction" : false,
+    "config" : { }
+  }, {
+    "alias" : "CONFIGURE_TOTP",
+    "name" : "Configure Totp",
+    "providerId" : "CONFIGURE_TOTP",
+    "enabled" : true,
+    "defaultAction" : false,
+    "config" : { }
+  }, {
+    "alias" : "UPDATE_PASSWORD",
+    "name" : "Update Password",
+    "providerId" : "UPDATE_PASSWORD",
+    "enabled" : true,
+    "defaultAction" : false,
+    "config" : { }
+  }, {
+    "alias" : "UPDATE_PROFILE",
+    "name" : "Update Profile",
+    "providerId" : "UPDATE_PROFILE",
+    "enabled" : true,
+    "defaultAction" : false,
+    "config" : { }
+  }, {
+    "alias" : "VERIFY_EMAIL",
+    "name" : "Verify Email",
+    "providerId" : "VERIFY_EMAIL",
+    "enabled" : true,
+    "defaultAction" : false,
+    "config" : { }
+  } ],
+  "browserFlow" : "browser",
+  "registrationFlow" : "registration",
+  "directGrantFlow" : "direct grant",
+  "resetCredentialsFlow" : "reset credentials",
+  "clientAuthenticationFlow" : "clients"
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml
index cec51eb..13a2059 100644
--- a/testsuite/integration-arquillian/tests/pom.xml
+++ b/testsuite/integration-arquillian/tests/pom.xml
@@ -36,6 +36,7 @@
         <arquillian-graphene.version>2.1.0.Alpha2</arquillian-graphene.version>
         <arquillian-wildfly-container.version>8.2.0.Final</arquillian-wildfly-container.version>
         <version.shrinkwrap.resolvers>2.1.1</version.shrinkwrap.resolvers>
+        <skip.unpack.previous>true</skip.unpack.previous>
     </properties>
 
     <dependencyManagement>
@@ -96,6 +97,32 @@
                         <failIfNoTests>false</failIfNoTests>
                     </configuration>
                 </plugin>
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-dependency-plugin</artifactId>
+                    <executions>
+                        <execution>
+                            <id>unpack-previous</id>
+                            <phase>generate-test-resources</phase>
+                            <goals>
+                                <goal>unpack</goal>
+                            </goals>
+                            <configuration>
+                                <skip>${skip.unpack.previous}</skip>
+                                <artifactItems>
+                                    <artifactItem>
+                                        <groupId>org.keycloak.testsuite</groupId>
+                                        <artifactId>${server.artifactId}</artifactId>
+                                        <version>${project.version}</version>
+                                        <type>zip</type>
+                                    </artifactItem>
+                                </artifactItems>
+                                <outputDirectory>${containers.home}</outputDirectory>
+                                <overWriteIfNewer>true</overWriteIfNewer>
+                            </configuration>
+                        </execution>
+                    </executions>
+                </plugin>
             </plugins>
         </pluginManagement>        
     </build>
@@ -203,7 +230,7 @@
                 </dependency>
                 <dependency>
                     <groupId>org.keycloak</groupId>
-                    <artifactId>keycloak-client-api</artifactId>
+                    <artifactId>keycloak-client-registration-api</artifactId>
                 </dependency>
                 <dependency>
                     <groupId>org.keycloak</groupId>
@@ -333,7 +360,7 @@
             <properties>
                 <auth.server.container>auth-server-wildfly</auth.server.container>
                 <auth.server.wildfly.home>${containers.home}/keycloak-${project.version}</auth.server.wildfly.home>
-                <startup.timeout.sec>150</startup.timeout.sec>
+                <startup.timeout.sec>300</startup.timeout.sec>
                 <adapter.test.props/>
             </properties>
             <dependencies>
@@ -348,7 +375,6 @@
                         <plugin>
                             <groupId>org.apache.maven.plugins</groupId>
                             <artifactId>maven-dependency-plugin</artifactId>
-                            <version>2.10</version>
                             <executions>
                                 <execution>
                                     <id>unpack</id>
@@ -393,7 +419,7 @@
             <properties>
                 <auth.server.container>auth-server-eap6</auth.server.container>
                 <auth.server.eap6.home>${containers.home}/keycloak-${project.version}</auth.server.eap6.home>
-                <startup.timeout.sec>150</startup.timeout.sec>
+                <startup.timeout.sec>300</startup.timeout.sec>
                 <adapter.test.props/>
             </properties>
             <dependencies>
@@ -409,7 +435,6 @@
                         <plugin>
                             <groupId>org.apache.maven.plugins</groupId>
                             <artifactId>maven-dependency-plugin</artifactId>
-                            <version>2.10</version>
                             <executions>
                                 <execution>
                                     <id>unpack</id>
@@ -454,46 +479,23 @@
         <profile>
             <id>migration-kc16</id>
             <properties>
-                <keycloak-1.6.0.Final.home>${containers.home}/keycloak-1.6.0.Final</keycloak-1.6.0.Final.home>
+                <skip.unpack.previous>false</skip.unpack.previous>
+                <server.version>1.6.1.Final</server.version>
+                <server.artifactId>integration-arquillian-server-wildfly-kc16</server.artifactId>
             </properties>
             <build>
                 <pluginManagement>
                     <plugins>
                         <plugin>
                             <groupId>org.apache.maven.plugins</groupId>
-                            <artifactId>maven-dependency-plugin</artifactId>
-                            <version>2.10</version>
-                            <executions>
-                                <execution>
-                                    <id>unpack-previous</id>
-                                    <phase>generate-test-resources</phase>
-                                    <goals>
-                                        <goal>unpack</goal>
-                                    </goals>
-                                    <configuration>
-                                        <artifactItems>
-                                            <artifactItem>
-                                                <groupId>org.keycloak.testsuite</groupId>
-                                                <artifactId>integration-arquillian-server-wildfly-kc16</artifactId>
-                                                <version>${project.version}</version>
-                                                <type>zip</type>
-                                            </artifactItem>
-                                        </artifactItems>
-                                        <outputDirectory>${containers.home}</outputDirectory>
-                                        <overWriteIfNewer>true</overWriteIfNewer>
-                                    </configuration>
-                                </execution>
-                            </executions>
-                        </plugin>
-                        <plugin>
-                            <groupId>org.apache.maven.plugins</groupId>
                             <artifactId>maven-surefire-plugin</artifactId>
                             <configuration>
                                 <systemPropertyVariables>
                                     <migration>true</migration>
                                     <migration.kc16>true</migration.kc16>
-                                    <keycloak-1.6.0.Final.home>${keycloak-1.6.0.Final.home}</keycloak-1.6.0.Final.home>
+                                    <keycloak.migration.home>${containers.home}/keycloak-${server.version}</keycloak.migration.home>
                                     <keycloak.migration.file>src/test/resources/migration-test/migration-realm-16.json</keycloak.migration.file>
+                                    <version>${server.version}</version>
                                 </systemPropertyVariables>
                             </configuration>
                         </plugin>
@@ -505,45 +507,23 @@
         <profile>
             <id>migration-kc15</id>
             <properties>
-                <keycloak-1.5.1.Final.home>${containers.home}/keycloak-1.5.1.Final</keycloak-1.5.1.Final.home>
+                <skip.unpack.previous>false</skip.unpack.previous>
+                <server.version>1.5.1.Final</server.version>
+                <server.artifactId>integration-arquillian-server-wildfly-kc15</server.artifactId>
             </properties>
             <build>
                 <pluginManagement>
                     <plugins>
                         <plugin>
                             <groupId>org.apache.maven.plugins</groupId>
-                            <artifactId>maven-dependency-plugin</artifactId>
-                            <version>2.10</version>
-                            <executions>
-                                <execution>
-                                    <id>unpack-previous</id>
-                                    <phase>generate-test-resources</phase>
-                                    <goals>
-                                        <goal>unpack</goal>
-                                    </goals>
-                                    <configuration>
-                                        <artifactItems>
-                                            <artifactItem>
-                                                <groupId>org.keycloak.testsuite</groupId>
-                                                <artifactId>integration-arquillian-server-wildfly-kc15</artifactId>
-                                                <version>${project.version}</version>
-                                                <type>zip</type>
-                                            </artifactItem>
-                                        </artifactItems>
-                                        <outputDirectory>${containers.home}</outputDirectory>
-                                        <overWriteIfNewer>true</overWriteIfNewer>
-                                    </configuration>
-                                </execution>
-                            </executions>
-                        </plugin>
-                        <plugin>
-                            <groupId>org.apache.maven.plugins</groupId>
                             <artifactId>maven-surefire-plugin</artifactId>
                             <configuration>
                                 <systemPropertyVariables>
                                     <migration>true</migration>
                                     <migration.kc15>true</migration.kc15>
-                                    <keycloak-1.5.1.Final.home>${keycloak-1.5.1.Final.home}</keycloak-1.5.1.Final.home>
+                                    <keycloak.migration.home>${containers.home}/keycloak-${server.version}</keycloak.migration.home>
+                                    <keycloak.migration.file>src/test/resources/migration-test/migration-realm-15.json</keycloak.migration.file>
+                                    <version>${server.version}</version>
                                 </systemPropertyVariables>
                             </configuration>
                         </plugin>
@@ -555,45 +535,21 @@
         <profile>
             <id>migration-kc14</id>
             <properties>
-                <keycloak-1.4.0.Final.home>${containers.home}/keycloak-1.4.0.Final</keycloak-1.4.0.Final.home>
+                <skip.unpack.previous>false</skip.unpack.previous>
+                <server.version>1.4.0.Final</server.version>
+                <server.artifactId>integration-arquillian-server-wildfly-kc14</server.artifactId>
             </properties>
             <build>
                 <pluginManagement>
                     <plugins>
                         <plugin>
                             <groupId>org.apache.maven.plugins</groupId>
-                            <artifactId>maven-dependency-plugin</artifactId>
-                            <version>2.10</version>
-                            <executions>
-                                <execution>
-                                    <id>unpack-previous</id>
-                                    <phase>generate-test-resources</phase>
-                                    <goals>
-                                        <goal>unpack</goal>
-                                    </goals>
-                                    <configuration>
-                                        <artifactItems>
-                                            <artifactItem>
-                                                <groupId>org.keycloak.testsuite</groupId>
-                                                <artifactId>integration-arquillian-server-wildfly-kc14</artifactId>
-                                                <version>${project.version}</version>
-                                                <type>zip</type>
-                                            </artifactItem>
-                                        </artifactItems>
-                                        <outputDirectory>${containers.home}</outputDirectory>
-                                        <overWriteIfNewer>true</overWriteIfNewer>
-                                    </configuration>
-                                </execution>
-                            </executions>
-                        </plugin>
-                        <plugin>
-                            <groupId>org.apache.maven.plugins</groupId>
                             <artifactId>maven-surefire-plugin</artifactId>
                             <configuration>
                                 <systemPropertyVariables>
                                     <migration>true</migration>
                                     <migration.kc14>true</migration.kc14>
-                                    <keycloak-1.4.0.Final.home>${keycloak-1.4.0.Final.home}</keycloak-1.4.0.Final.home>
+                                    <keycloak.migration.home>${containers.home}/keycloak-${server.version}</keycloak.migration.home>
                                 </systemPropertyVariables>
                             </configuration>
                         </plugin>
@@ -605,45 +561,21 @@
         <profile>
             <id>migration-kc13</id>
             <properties>
-                <keycloak-1.3.1.Final.home>${containers.home}/keycloak-1.3.1.Final</keycloak-1.3.1.Final.home>
+                <skip.unpack.previous>false</skip.unpack.previous>
+                <server.version>1.3.1.Final</server.version>
+                <server.artifactId>integration-arquillian-server-wildfly-kc13</server.artifactId>
             </properties>
             <build>
                 <pluginManagement>
                     <plugins>
                         <plugin>
                             <groupId>org.apache.maven.plugins</groupId>
-                            <artifactId>maven-dependency-plugin</artifactId>
-                            <version>2.10</version>
-                            <executions>
-                                <execution>
-                                    <id>unpack-previous</id>
-                                    <phase>generate-test-resources</phase>
-                                    <goals>
-                                        <goal>unpack</goal>
-                                    </goals>
-                                    <configuration>
-                                        <artifactItems>
-                                            <artifactItem>
-                                                <groupId>org.keycloak.testsuite</groupId>
-                                                <artifactId>integration-arquillian-server-wildfly-kc13</artifactId>
-                                                <version>${project.version}</version>
-                                                <type>zip</type>
-                                            </artifactItem>
-                                        </artifactItems>
-                                        <outputDirectory>${containers.home}</outputDirectory>
-                                        <overWriteIfNewer>true</overWriteIfNewer>
-                                    </configuration>
-                                </execution>
-                            </executions>
-                        </plugin>
-                        <plugin>
-                            <groupId>org.apache.maven.plugins</groupId>
                             <artifactId>maven-surefire-plugin</artifactId>
                             <configuration>
                                 <systemPropertyVariables>
                                     <migration>true</migration>
                                     <migration.kc13>true</migration.kc13>
-                                    <keycloak-1.3.1.Final.home>${keycloak-1.3.1.Final.home}</keycloak-1.3.1.Final.home>
+                                    <keycloak.migration.home>${containers.home}/keycloak-${server.version}</keycloak.migration.home>
                                 </systemPropertyVariables>
                             </configuration>
                         </plugin>
@@ -655,45 +587,21 @@
         <profile>
             <id>migration-kc12</id>
             <properties>
-                <keycloak-1.2.0.Final.home>${containers.home}/keycloak-1.2.0.Final</keycloak-1.2.0.Final.home>
+                <skip.unpack.previous>false</skip.unpack.previous>
+                <server.version>1.2.0.Final</server.version>
+                <server.artifactId>integration-arquillian-server-wildfly-kc12</server.artifactId>
             </properties>
             <build>
                 <pluginManagement>
                     <plugins>
                         <plugin>
                             <groupId>org.apache.maven.plugins</groupId>
-                            <artifactId>maven-dependency-plugin</artifactId>
-                            <version>2.10</version>
-                            <executions>
-                                <execution>
-                                    <id>unpack-previous</id>
-                                    <phase>generate-test-resources</phase>
-                                    <goals>
-                                        <goal>unpack</goal>
-                                    </goals>
-                                    <configuration>
-                                        <artifactItems>
-                                            <artifactItem>
-                                                <groupId>org.keycloak.testsuite</groupId>
-                                                <artifactId>integration-arquillian-server-wildfly-kc12</artifactId>
-                                                <version>${project.version}</version>
-                                                <type>zip</type>
-                                            </artifactItem>
-                                        </artifactItems>
-                                        <outputDirectory>${containers.home}</outputDirectory>
-                                        <overWriteIfNewer>true</overWriteIfNewer>
-                                    </configuration>
-                                </execution>
-                            </executions>
-                        </plugin>
-                        <plugin>
-                            <groupId>org.apache.maven.plugins</groupId>
                             <artifactId>maven-surefire-plugin</artifactId>
                             <configuration>
                                 <systemPropertyVariables>
                                     <migration>true</migration>
                                     <migration.kc12>true</migration.kc12>
-                                    <keycloak-1.2.0.Final.home>${keycloak-1.2.0.Final.home}</keycloak-1.2.0.Final.home>
+                                    <keycloak.migration.home>${containers.home}/keycloak-${server.version}</keycloak.migration.home>
                                 </systemPropertyVariables>
                             </configuration>
                         </plugin>
diff --git a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/testsaml.json b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/testsaml.json
index 0b13fb9..04c5dcd 100755
--- a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/testsaml.json
+++ b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/testsaml.json
@@ -40,6 +40,30 @@
                 { "type" : "password",
                     "value" : "password" }
             ]
+        },
+        {
+            "username" : "topGroupUser",
+            "enabled": true,
+            "email" : "top@redhat.com",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "password" }
+            ],
+            "groups": [
+                "/top"
+            ]
+        },
+        {
+            "username" : "level2GroupUser",
+            "enabled": true,
+            "email" : "level2@redhat.com",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "password" }
+            ],
+            "groups": [
+                "/top/level2"
+            ]
         }
     ],
     "applications": [
@@ -347,6 +371,26 @@
             }
         }
     ],
+    "groups" : [
+        {
+            "name": "top",
+            "attributes": {
+                "topAttribute": ["true"]
+
+            },
+            "realmRoles": ["manager"],
+            "subGroups": [
+                {
+                    "name": "level2",
+                    "realmRoles": ["user"],
+                    "attributes": {
+                        "level2Attribute": ["true"]
+
+                    }
+                }
+            ]
+        }
+    ],
     "roles" : {
         "realm" : [
             {
diff --git a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/testsaml.json b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/testsaml.json
index 0b13fb9..04c5dcd 100755
--- a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/testsaml.json
+++ b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/testsaml.json
@@ -40,6 +40,30 @@
                 { "type" : "password",
                     "value" : "password" }
             ]
+        },
+        {
+            "username" : "topGroupUser",
+            "enabled": true,
+            "email" : "top@redhat.com",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "password" }
+            ],
+            "groups": [
+                "/top"
+            ]
+        },
+        {
+            "username" : "level2GroupUser",
+            "enabled": true,
+            "email" : "level2@redhat.com",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "password" }
+            ],
+            "groups": [
+                "/top/level2"
+            ]
         }
     ],
     "applications": [
@@ -347,6 +371,26 @@
             }
         }
     ],
+    "groups" : [
+        {
+            "name": "top",
+            "attributes": {
+                "topAttribute": ["true"]
+
+            },
+            "realmRoles": ["manager"],
+            "subGroups": [
+                {
+                    "name": "level2",
+                    "realmRoles": ["user"],
+                    "attributes": {
+                        "level2Attribute": ["true"]
+
+                    }
+                }
+            ]
+        }
+    ],
     "roles" : {
         "realm" : [
             {
diff --git a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/testsaml.json b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/testsaml.json
index 0b13fb9..04c5dcd 100755
--- a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/testsaml.json
+++ b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/testsaml.json
@@ -40,6 +40,30 @@
                 { "type" : "password",
                     "value" : "password" }
             ]
+        },
+        {
+            "username" : "topGroupUser",
+            "enabled": true,
+            "email" : "top@redhat.com",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "password" }
+            ],
+            "groups": [
+                "/top"
+            ]
+        },
+        {
+            "username" : "level2GroupUser",
+            "enabled": true,
+            "email" : "level2@redhat.com",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "password" }
+            ],
+            "groups": [
+                "/top/level2"
+            ]
         }
     ],
     "applications": [
@@ -347,6 +371,26 @@
             }
         }
     ],
+    "groups" : [
+        {
+            "name": "top",
+            "attributes": {
+                "topAttribute": ["true"]
+
+            },
+            "realmRoles": ["manager"],
+            "subGroups": [
+                {
+                    "name": "level2",
+                    "realmRoles": ["user"],
+                    "attributes": {
+                        "level2Attribute": ["true"]
+
+                    }
+                }
+            ]
+        }
+    ],
     "roles" : {
         "realm" : [
             {
diff --git a/testsuite/tomcat6/src/test/resources/keycloak-saml/testsaml.json b/testsuite/tomcat6/src/test/resources/keycloak-saml/testsaml.json
index 0b13fb9..04c5dcd 100755
--- a/testsuite/tomcat6/src/test/resources/keycloak-saml/testsaml.json
+++ b/testsuite/tomcat6/src/test/resources/keycloak-saml/testsaml.json
@@ -40,6 +40,30 @@
                 { "type" : "password",
                     "value" : "password" }
             ]
+        },
+        {
+            "username" : "topGroupUser",
+            "enabled": true,
+            "email" : "top@redhat.com",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "password" }
+            ],
+            "groups": [
+                "/top"
+            ]
+        },
+        {
+            "username" : "level2GroupUser",
+            "enabled": true,
+            "email" : "level2@redhat.com",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "password" }
+            ],
+            "groups": [
+                "/top/level2"
+            ]
         }
     ],
     "applications": [
@@ -347,6 +371,26 @@
             }
         }
     ],
+    "groups" : [
+        {
+            "name": "top",
+            "attributes": {
+                "topAttribute": ["true"]
+
+            },
+            "realmRoles": ["manager"],
+            "subGroups": [
+                {
+                    "name": "level2",
+                    "realmRoles": ["user"],
+                    "attributes": {
+                        "level2Attribute": ["true"]
+
+                    }
+                }
+            ]
+        }
+    ],
     "roles" : {
         "realm" : [
             {
diff --git a/testsuite/tomcat7/src/test/resources/keycloak-saml/testsaml.json b/testsuite/tomcat7/src/test/resources/keycloak-saml/testsaml.json
index 0b13fb9..04c5dcd 100755
--- a/testsuite/tomcat7/src/test/resources/keycloak-saml/testsaml.json
+++ b/testsuite/tomcat7/src/test/resources/keycloak-saml/testsaml.json
@@ -40,6 +40,30 @@
                 { "type" : "password",
                     "value" : "password" }
             ]
+        },
+        {
+            "username" : "topGroupUser",
+            "enabled": true,
+            "email" : "top@redhat.com",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "password" }
+            ],
+            "groups": [
+                "/top"
+            ]
+        },
+        {
+            "username" : "level2GroupUser",
+            "enabled": true,
+            "email" : "level2@redhat.com",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "password" }
+            ],
+            "groups": [
+                "/top/level2"
+            ]
         }
     ],
     "applications": [
@@ -347,6 +371,26 @@
             }
         }
     ],
+    "groups" : [
+        {
+            "name": "top",
+            "attributes": {
+                "topAttribute": ["true"]
+
+            },
+            "realmRoles": ["manager"],
+            "subGroups": [
+                {
+                    "name": "level2",
+                    "realmRoles": ["user"],
+                    "attributes": {
+                        "level2Attribute": ["true"]
+
+                    }
+                }
+            ]
+        }
+    ],
     "roles" : {
         "realm" : [
             {
diff --git a/testsuite/tomcat8/src/test/resources/keycloak-saml/testsaml.json b/testsuite/tomcat8/src/test/resources/keycloak-saml/testsaml.json
index 0b13fb9..04c5dcd 100755
--- a/testsuite/tomcat8/src/test/resources/keycloak-saml/testsaml.json
+++ b/testsuite/tomcat8/src/test/resources/keycloak-saml/testsaml.json
@@ -40,6 +40,30 @@
                 { "type" : "password",
                     "value" : "password" }
             ]
+        },
+        {
+            "username" : "topGroupUser",
+            "enabled": true,
+            "email" : "top@redhat.com",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "password" }
+            ],
+            "groups": [
+                "/top"
+            ]
+        },
+        {
+            "username" : "level2GroupUser",
+            "enabled": true,
+            "email" : "level2@redhat.com",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "password" }
+            ],
+            "groups": [
+                "/top/level2"
+            ]
         }
     ],
     "applications": [
@@ -347,6 +371,26 @@
             }
         }
     ],
+    "groups" : [
+        {
+            "name": "top",
+            "attributes": {
+                "topAttribute": ["true"]
+
+            },
+            "realmRoles": ["manager"],
+            "subGroups": [
+                {
+                    "name": "level2",
+                    "realmRoles": ["user"],
+                    "attributes": {
+                        "level2Attribute": ["true"]
+
+                    }
+                }
+            ]
+        }
+    ],
     "roles" : {
         "realm" : [
             {