keycloak-memoizeit

Changes

connections/file/pom.xml 43(+0 -43)

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)

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/GroupAdapter.java 213(+0 -213)

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 1840(+0 -1840)

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 614(+0 -614)

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

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

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 20(+5 -15)

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

Details

diff --git a/client-api/src/main/java/org/keycloak/client/registration/Auth.java b/client-api/src/main/java/org/keycloak/client/registration/Auth.java
new file mode 100644
index 0000000..5b0e85f
--- /dev/null
+++ b/client-api/src/main/java/org/keycloak/client/registration/Auth.java
@@ -0,0 +1,58 @@
+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.ClientRepresentation;
+
+/**
+ * @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(ClientRepresentation 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-api/src/main/java/org/keycloak/client/registration/ClientRegistration.java b/client-api/src/main/java/org/keycloak/client/registration/ClientRegistration.java
index 82a3b37..e59de76 100644
--- a/client-api/src/main/java/org/keycloak/client/registration/ClientRegistration.java
+++ b/client-api/src/main/java/org/keycloak/client/registration/ClientRegistration.java
@@ -1,18 +1,9 @@
 package org.keycloak.client.registration;
 
-import org.apache.http.HttpHeaders;
-import org.apache.http.HttpRequest;
-import org.apache.http.HttpResponse;
 import org.apache.http.client.HttpClient;
-import org.apache.http.client.methods.HttpDelete;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.client.methods.HttpPut;
-import org.apache.http.entity.StringEntity;
-import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClients;
+import org.keycloak.representations.adapters.config.AdapterConfig;
 import org.keycloak.representations.idm.ClientRepresentation;
-import org.keycloak.common.util.Base64;
 import org.keycloak.util.JsonSerialization;
 
 import java.io.IOException;
@@ -23,160 +14,58 @@ import java.io.InputStream;
  */
 public class ClientRegistration {
 
-    private String clientRegistrationUrl;
-    private HttpClient httpClient;
-    private Auth auth;
+    private final String DEFAULT = "default";
+    private final String INSTALLATION = "install";
 
-    public static ClientRegistrationBuilder create() {
-        return new ClientRegistrationBuilder();
-    }
+    private HttpUtil httpUtil;
 
-    private ClientRegistration() {
+    public ClientRegistration(String authServerUrl, String realm) {
+        httpUtil = new HttpUtil(HttpClients.createDefault(), HttpUtil.getUrl(authServerUrl, "realms", realm, "clients"));
     }
 
-    public ClientRepresentation create(ClientRepresentation client) throws ClientRegistrationException {
-        String content = serialize(client);
-        InputStream resultStream = doPost(content);
-        return deserialize(resultStream, ClientRepresentation.class);
+    public ClientRegistration(String authServerUrl, String realm, HttpClient httpClient) {
+        httpUtil = new HttpUtil(httpClient, HttpUtil.getUrl(authServerUrl, "realms", realm, "clients"));
     }
 
-    public ClientRepresentation get() throws ClientRegistrationException {
-        if (auth instanceof ClientIdSecretAuth) {
-            String clientId = ((ClientIdSecretAuth) auth).clientId;
-            return get(clientId);
-        } else {
-            throw new ClientRegistrationException("Requires client authentication");
+    public void close() throws ClientRegistrationException {
+        if (httpUtil != null) {
+            httpUtil.close();
         }
+        httpUtil = null;
     }
 
-    public ClientRepresentation get(String clientId) throws ClientRegistrationException {
-        InputStream resultStream = doGet(clientId);
-        return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null;
+    public ClientRegistration auth(Auth auth) {
+        httpUtil.setAuth(auth);
+        return this;
     }
 
-    public void update(ClientRepresentation client) throws ClientRegistrationException {
+    public ClientRepresentation create(ClientRepresentation client) throws ClientRegistrationException {
         String content = serialize(client);
-        doPut(content, client.getClientId());
-    }
-
-    public void delete() throws ClientRegistrationException {
-        if (auth instanceof ClientIdSecretAuth) {
-            String clientId = ((ClientIdSecretAuth) auth).clientId;
-            delete(clientId);
-        } else {
-            throw new ClientRegistrationException("Requires client authentication");
-        }
+        InputStream resultStream = httpUtil.doPost(content, DEFAULT);
+        return deserialize(resultStream, ClientRepresentation.class);
     }
 
-    public void delete(String clientId) throws ClientRegistrationException {
-        doDelete(clientId);
+    public ClientRepresentation get(String clientId) throws ClientRegistrationException {
+        InputStream resultStream = httpUtil.doGet(DEFAULT, clientId);
+        return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null;
     }
 
-    public void close() throws ClientRegistrationException {
-        if (httpClient instanceof CloseableHttpClient) {
-            try {
-                ((CloseableHttpClient) httpClient).close();
-            } catch (IOException e) {
-                throw new ClientRegistrationException("Failed to close http client", e);
-            }
-        }
+    public AdapterConfig getAdapterConfig(String clientId) throws ClientRegistrationException {
+        InputStream resultStream = httpUtil.doGet(INSTALLATION, clientId);
+        return resultStream != null ? deserialize(resultStream, AdapterConfig.class) : null;
     }
 
-    private InputStream doPost(String content) throws ClientRegistrationException {
-        try {
-            HttpPost request = new HttpPost(clientRegistrationUrl);
-
-            request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
-            request.setHeader(HttpHeaders.ACCEPT, "application/json");
-            request.setEntity(new StringEntity(content));
-
-            auth.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);
-        }
-    }
-
-    private InputStream doGet(String endpoint) throws ClientRegistrationException {
-        try {
-            HttpGet request = new HttpGet(clientRegistrationUrl + "/" + endpoint);
-
-            request.setHeader(HttpHeaders.ACCEPT, "application/json");
-
-            auth.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 {
-                responseStream.close();
-                throw new HttpErrorException(response.getStatusLine());
-            }
-        } catch (IOException e) {
-            throw new ClientRegistrationException("Failed to send request", e);
-        }
+    public void update(ClientRepresentation client) throws ClientRegistrationException {
+        String content = serialize(client);
+        httpUtil.doPut(content, DEFAULT, client.getClientId());
     }
 
-    private void doPut(String content, String endpoint) throws ClientRegistrationException {
-        try {
-            HttpPut request = new HttpPut(clientRegistrationUrl + "/" + endpoint);
-
-            request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
-            request.setHeader(HttpHeaders.ACCEPT, "application/json");
-            request.setEntity(new StringEntity(content));
-
-            auth.addAuth(request);
-
-            HttpResponse response = httpClient.execute(request);
-            if (response.getEntity() != null) {
-                response.getEntity().getContent().close();
-            }
-
-            if (response.getStatusLine().getStatusCode() != 200) {
-                throw new HttpErrorException(response.getStatusLine());
-            }
-        } catch (IOException e) {
-            throw new ClientRegistrationException("Failed to send request", e);
-        }
+    public void delete(ClientRepresentation client) throws ClientRegistrationException {
+        delete(client.getClientId());
     }
 
-    private void doDelete(String endpoint) throws ClientRegistrationException {
-        try {
-            HttpDelete request = new HttpDelete(clientRegistrationUrl + "/" + endpoint);
-
-            auth.addAuth(request);
-
-            HttpResponse response = httpClient.execute(request);
-            if (response.getEntity() != null) {
-                response.getEntity().getContent().close();
-            }
-
-            if (response.getStatusLine().getStatusCode() != 200) {
-                throw new HttpErrorException(response.getStatusLine());
-            }
-        } catch (IOException e) {
-            throw new ClientRegistrationException("Failed to send request", e);
-        }
+    public void delete(String clientId) throws ClientRegistrationException {
+        httpUtil.doDelete(DEFAULT, clientId);
     }
 
     private String serialize(ClientRepresentation client) throws ClientRegistrationException {
@@ -195,81 +84,4 @@ public class ClientRegistration {
         }
     }
 
-    public static class ClientRegistrationBuilder {
-
-        private String realm;
-
-        private String authServerUrl;
-
-        private Auth auth;
-
-        private HttpClient httpClient;
-
-        public ClientRegistrationBuilder realm(String realm) {
-            this.realm = realm;
-            return this;
-        }
-        public ClientRegistrationBuilder authServerUrl(String authServerUrl) {
-            this.authServerUrl = authServerUrl;
-            return this;
-        }
-
-        public ClientRegistrationBuilder auth(String token) {
-            this.auth = new TokenAuth(token);
-            return this;
-        }
-
-        public ClientRegistrationBuilder auth(String clientId, String clientSecret) {
-            this.auth = new ClientIdSecretAuth(clientId, clientSecret);
-            return this;
-        }
-
-        public ClientRegistrationBuilder httpClient(HttpClient httpClient) {
-            this.httpClient = httpClient;
-            return this;
-        }
-
-        public ClientRegistration build() {
-            ClientRegistration clientRegistration = new ClientRegistration();
-            clientRegistration.clientRegistrationUrl = authServerUrl + "/realms/" + realm + "/client-registration/default";
-
-            clientRegistration.httpClient = httpClient != null ? httpClient : HttpClients.createDefault();
-            clientRegistration.auth = auth;
-
-            return clientRegistration;
-        }
-
-    }
-
-    public interface Auth {
-        void addAuth(HttpRequest httpRequest);
-    }
-
-    public static class AuthorizationHeaderAuth implements Auth {
-        private String credentials;
-
-        public AuthorizationHeaderAuth(String credentials) {
-            this.credentials = credentials;
-        }
-
-        public void addAuth(HttpRequest httpRequest) {
-            httpRequest.setHeader(HttpHeaders.AUTHORIZATION, credentials);
-        }
-    }
-
-    public static class TokenAuth extends AuthorizationHeaderAuth {
-        public TokenAuth(String token) {
-            super("Bearer " + token);
-        }
-    }
-
-    public static class ClientIdSecretAuth extends AuthorizationHeaderAuth {
-        private String clientId;
-
-        public ClientIdSecretAuth(String clientId, String clientSecret) {
-            super("Basic " + Base64.encodeBytes((clientId + ":" + clientSecret).getBytes()));
-            this.clientId = clientId;
-        }
-    }
-
 }
diff --git a/client-api/src/main/java/org/keycloak/client/registration/HttpUtil.java b/client-api/src/main/java/org/keycloak/client/registration/HttpUtil.java
new file mode 100644
index 0000000..699d378
--- /dev/null
+++ b/client-api/src/main/java/org/keycloak/client/registration/HttpUtil.java
@@ -0,0 +1,159 @@
+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... path) throws ClientRegistrationException {
+        try {
+            HttpPost request = new HttpPost(getUrl(baseUri, path));
+
+            request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
+            request.setHeader(HttpHeaders.ACCEPT, "application/json");
+            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... path) throws ClientRegistrationException {
+        try {
+            HttpGet request = new HttpGet(getUrl(baseUri, path));
+
+            request.setHeader(HttpHeaders.ACCEPT, "application/json");
+
+            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 {
+                responseStream.close();
+                throw new HttpErrorException(response.getStatusLine());
+            }
+        } catch (IOException e) {
+            throw new ClientRegistrationException("Failed to send request", e);
+        }
+    }
+
+    void doPut(String content, String... path) throws ClientRegistrationException {
+        try {
+            HttpPut request = new HttpPut(getUrl(baseUri, path));
+
+            request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
+            request.setHeader(HttpHeaders.ACCEPT, "application/json");
+            request.setEntity(new StringEntity(content));
+
+            addAuth(request);
+
+            HttpResponse response = httpClient.execute(request);
+            if (response.getEntity() != null) {
+                response.getEntity().getContent().close();
+            }
+
+            if (response.getStatusLine().getStatusCode() != 200) {
+                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() != 200) {
+                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/common/src/main/java/org/keycloak/common/util/ObjectUtil.java b/common/src/main/java/org/keycloak/common/util/ObjectUtil.java
index 1ade852..bec8acf 100644
--- a/common/src/main/java/org/keycloak/common/util/ObjectUtil.java
+++ b/common/src/main/java/org/keycloak/common/util/ObjectUtil.java
@@ -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-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
index 1120252..aed99fc 100755
--- 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
@@ -58,6 +58,9 @@
         <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"/>
 
+        <addColumn tableName="CLIENT">
+            <column name="REGISTRATION_SECRET" type="VARCHAR(255)"/>
+        </addColumn>
 
     </changeSet>
 </databaseChangeLog>
\ No newline at end of file
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/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
index cd5292f..4414f24 100755
--- a/core/src/main/java/org/keycloak/representations/idm/GroupRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/GroupRepresentation.java
@@ -14,6 +14,7 @@ import java.util.Map;
 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;
@@ -35,6 +36,14 @@ public class GroupRepresentation {
         this.name = name;
     }
 
+    public String getPath() {
+        return path;
+    }
+
+    public void setPath(String path) {
+        this.path = path;
+    }
+
     public List<String> getRealmRoles() {
         return realmRoles;
     }
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/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/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/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/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/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 34c5979..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";
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 5cffe78..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),
 
@@ -66,8 +68,6 @@ public enum EventType {
     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/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 3d2eeea..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.
 
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 ebf3a83..bcbc98d 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
@@ -394,7 +394,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.
+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
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 2fd5382..fe74ff2 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
@@ -447,6 +447,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 : {
@@ -628,9 +643,21 @@ module.config([ '$routeProvider', function($routeProvider) {
                 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 : {
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..c5f6316 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
@@ -877,7 +877,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
index 7e82f32..4e3b986 100755
--- 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
@@ -319,3 +319,50 @@ module.controller('GroupRoleMappingCtrl', function($scope, $http, realm, group, 
 
 
 });
+
+module.controller('GroupMembersCtrl', function($scope, realm, group, GroupMembership) {
+    $scope.realm = realm;
+    $scope.page = 0;
+
+
+    $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();
+
+});
+
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/services.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
index b9e4705..065f831 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
@@ -1512,6 +1512,34 @@ module.factory('GroupCompositeClientRoleMapping', function($resource) {
     });
 });
 
+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'
+        }
+    });
+});
+
+
 
 
 
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/group-members.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-members.html
new file mode 100755
index 0000000..6c20930
--- /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>Actions</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/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-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-idp-link-confirm.ftl b/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-confirm.ftl
index 02923d9..f77eb38 100644
--- 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
@@ -12,8 +12,8 @@
         <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" value="updateProfile">${msg("confirmLinkIdpReviewProfile")}</button>
-                    <button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="submitAction" value="linkAccount">${msg("confirmLinkIdpContinue", idpAlias)}</button>
+                    <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>
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
index ab3e83e..0ba0686 100644
--- 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
@@ -5,10 +5,10 @@
     <#elseif section = "header">
         ${msg("emailLinkIdpTitle", idpAlias)}
     <#elseif section = "form">
-        <p class="instruction">
+        <p id="instruction1" class="instruction">
             ${msg("emailLinkIdp1", idpAlias, brokerContext.username, realm.name)}
         </p>
-        <p class="instruction">
+        <p id="instruction2" class="instruction">
             ${msg("emailLinkIdp2")} <a href="${url.firstBrokerLoginUrl}">${msg("doClickHere")}</a> ${msg("emailLinkIdp3")}
         </p>
     </#if>
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 803ef2d..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
@@ -185,7 +185,6 @@ 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.
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 3a04fbf..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
@@ -21,6 +21,7 @@ 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;
@@ -89,7 +90,7 @@ 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);
@@ -102,12 +103,12 @@ 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);
 
         BrokeredIdentityContext brokerContext = (BrokeredIdentityContext) this.attributes.get(IDENTITY_PROVIDER_BROKER_CONTEXT);
         String idpAlias = brokerContext.getIdpConfig().getAlias();
-        idpAlias = idpAlias.substring(0, 1).toUpperCase() + idpAlias.substring(1);
+        idpAlias = ObjectUtil.capitalize(idpAlias);
 
         attributes.put("identityProviderContext", brokerContext);
         attributes.put("identityProviderAlias", idpAlias);
@@ -123,7 +124,7 @@ 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("executeActionsSubject", "executeActions.ftl", attributes);
@@ -137,7 +138,7 @@ 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);
@@ -253,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-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
index ef8cc74..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
@@ -22,6 +22,7 @@ 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;
@@ -288,7 +289,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
             case LOGIN_IDP_LINK_EMAIL:
                 BrokeredIdentityContext brokerContext = (BrokeredIdentityContext) this.attributes.get(IDENTITY_PROVIDER_BROKER_CONTEXT);
                 String idpAlias = brokerContext.getIdpConfig().getAlias();
-                idpAlias = idpAlias.substring(0, 1).toUpperCase() + idpAlias.substring(1);
+                idpAlias = ObjectUtil.capitalize(idpAlias);
 
                 attributes.put("brokerContext", brokerContext);
                 attributes.put("idpAlias", idpAlias);
@@ -470,7 +471,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
     public Response createIdpLinkEmailPage() {
         BrokeredIdentityContext brokerContext = (BrokeredIdentityContext) this.attributes.get(IDENTITY_PROVIDER_BROKER_CONTEXT);
         String idpAlias = brokerContext.getIdpConfig().getAlias();
-        idpAlias = idpAlias.substring(0, 1).toUpperCase() + idpAlias.substring(1);
+        idpAlias = ObjectUtil.capitalize(idpAlias);;
         setMessage(MessageType.WARNING, Messages.LINK_IDP, idpAlias);
 
         return createResponse(LoginFormsPages.LOGIN_IDP_LINK_EMAIL);
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-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/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/model/api/src/main/java/org/keycloak/models/ClientModel.java b/model/api/src/main/java/org/keycloak/models/ClientModel.java
index 8753f45..c421aea 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 getRegistrationSecret();
+    void setRegistrationSecret(String registrationSecret);
+
     boolean isFullScopeAllowed();
     void setFullScopeAllowed(boolean value);
 
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..f15614f 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 registrationSecret;
     private String protocol;
     private int notBefore;
     private boolean publicClient;
@@ -90,6 +91,14 @@ public class ClientEntity extends AbstractIdentifiableEntity {
         this.secret = secret;
     }
 
+    public String getRegistrationSecret() {
+        return registrationSecret;
+    }
+
+    public void setRegistrationSecret(String registrationSecret) {
+        this.registrationSecret = registrationSecret;
+    }
+
     public int getNotBefore() {
         return notBefore;
     }
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/utils/DefaultAuthenticationFlows.java b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
index a874f5e..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,6 +1,8 @@
 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;
@@ -25,8 +27,10 @@ public class DefaultAuthenticationFlows {
 
     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);
@@ -34,7 +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);
+        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);
@@ -42,7 +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);
+        if (realm.getFlowByAlias(FIRST_BROKER_LOGIN_FLOW) == null) firstBrokerLoginFlow(realm, true);
     }
 
     public static void registrationFlow(RealmModel realm) {
@@ -320,7 +324,7 @@ public class DefaultAuthenticationFlows {
         realm.addAuthenticatorExecution(execution);
     }
 
-    public static void firstBrokerLoginFlow(RealmModel realm) {
+    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");
@@ -347,7 +351,7 @@ public class DefaultAuthenticationFlows {
 
 
         AuthenticatorConfigModel createUserIfUniqueConfig = new AuthenticatorConfigModel();
-        createUserIfUniqueConfig.setAlias("create unique user config");
+        createUserIfUniqueConfig.setAlias(IDP_CREATE_UNIQUE_USER_CONFIG_ALIAS);
         config = new HashMap<>();
         config.put("require.password.update.after.registration", "false");
         createUserIfUniqueConfig.setConfig(config);
@@ -366,7 +370,7 @@ public class DefaultAuthenticationFlows {
         AuthenticationFlowModel linkExistingAccountFlow = new AuthenticationFlowModel();
         linkExistingAccountFlow.setTopLevel(false);
         linkExistingAccountFlow.setBuiltIn(true);
-        linkExistingAccountFlow.setAlias("Handle Existing Account");
+        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);
@@ -421,10 +425,19 @@ public class DefaultAuthenticationFlows {
         execution = new AuthenticationExecutionModel();
         execution.setParentFlow(verifyByReauthenticationAccountFlow.getId());
         execution.setRequirement(AuthenticationExecutionModel.Requirement.OPTIONAL);
-        // TODO: read the requirement from browser authenticator
-//        if (migrate && hasCredentialType(realm, RequiredCredentialModel.TOTP.getType())) {
-//            execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
-//        }
+
+        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);
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 c2fd73e..ad5997a 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,6 +1,8 @@
 package org.keycloak.models.utils;
 
 import org.bouncycastle.openssl.PEMWriter;
+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;
@@ -16,6 +18,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;
@@ -31,6 +34,7 @@ import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.security.cert.X509Certificate;
 import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -386,4 +390,24 @@ 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);
+            }
+        }
+    }
 }
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 c62d96e..0df3516 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
@@ -11,8 +11,6 @@ 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;
@@ -47,7 +45,6 @@ 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;
@@ -60,10 +57,25 @@ import java.util.Set;
  * @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();
@@ -375,6 +387,7 @@ public class ModelToRepresentation {
         rep.setNotBefore(clientModel.getNotBefore());
         rep.setNodeReRegistrationTimeout(clientModel.getNodeReRegistrationTimeout());
         rep.setClientAuthenticatorType(clientModel.getClientAuthenticatorType());
+        rep.setRegistrationAccessToken(clientModel.getRegistrationSecret());
 
         Set<String> redirectUris = clientModel.getRedirectUris();
         if (redirectUris != null) {
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 a31d355..a46ee57 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
@@ -737,6 +737,8 @@ public class RepresentationToModel {
             KeycloakModelUtils.generateSecret(client);
         }
 
+        client.setRegistrationSecret(resourceRep.getRegistrationAccessToken());
+
         if (resourceRep.getAttributes() != null) {
             for (Map.Entry<String, String> entry : resourceRep.getAttributes().entrySet()) {
                 client.setAttribute(entry.getKey(), entry.getValue());
@@ -813,6 +815,7 @@ public class RepresentationToModel {
         if (rep.isSurrogateAuthRequired() != null) resource.setSurrogateAuthRequired(rep.isSurrogateAuthRequired());
         if (rep.getNodeReRegistrationTimeout() != null) resource.setNodeReRegistrationTimeout(rep.getNodeReRegistrationTimeout());
         if (rep.getClientAuthenticatorType() != null) resource.setClientAuthenticatorType(rep.getClientAuthenticatorType());
+        if (rep.getRegistrationAccessToken() != null) resource.setRegistrationSecret(rep.getRegistrationAccessToken());
         resource.updateClient();
 
         if (rep.getProtocol() != null) resource.setProtocol(rep.getProtocol());
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..7a87373 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 getRegistrationSecret() {
+        if (updated != null) return updated.getRegistrationSecret();
+        return cached.getRegistrationSecret();
+    }
+
+    public void setRegistrationSecret(String registrationsecret) {
+        getDelegateForUpdate();
+        updated.setRegistrationSecret(registrationsecret);
+    }
 
     public boolean isPublicClient() {
         if (updated != null) return updated.isPublicClient();
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..1c04b9d 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 registrationSecret;
     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();
+        registrationSecret = model.getRegistrationSecret();
         clientId = model.getClientId();
         name = model.getName();
         description = model.getDescription();
@@ -129,6 +131,10 @@ public class CachedClient implements Serializable {
         return secret;
     }
 
+    public String getRegistrationSecret() {
+        return registrationSecret;
+    }
+
     public boolean isPublicClient() {
         return publicClient;
     }
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..5ea0b11 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 getRegistrationSecret() {
+        return entity.getRegistrationSecret();
+    }
+
+    @Override
+    public void setRegistrationSecret(String registrationSecret) {
+        entity.setRegistrationSecret(registrationSecret);
+    }
+
+    @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..881b129 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_SECRET")
+    private String registrationSecret;
     @Column(name="CLIENT_AUTHENTICATOR_TYPE")
     private String clientAuthenticatorType;
     @Column(name="NOT_BEFORE")
@@ -201,6 +203,14 @@ public class ClientEntity {
         this.secret = secret;
     }
 
+    public String getRegistrationSecret() {
+        return registrationSecret;
+    }
+
+    public void setRegistrationSecret(String registrationSecret) {
+        this.registrationSecret = registrationSecret;
+    }
+
     public int getNotBefore() {
         return notBefore;
     }
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 4a26cd8..7b8e3a6 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,5 +1,6 @@
 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;
@@ -1195,8 +1196,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
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..cbacd09 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 getRegistrationSecret() {
+        return getMongoEntity().getRegistrationSecret();
+    }
+
+    @Override
+    public void setRegistrationSecret(String registrationSecretsecret) {
+        getMongoEntity().setRegistrationSecret(registrationSecretsecret);
+        updateMongoEntity();
+    }
+
+    @Override
     public boolean isPublicClient() {
         return getMongoEntity().isPublicClient();
     }
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 59bc553..30266af 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
@@ -1223,7 +1223,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 87729d4..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
@@ -453,7 +453,7 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
 
     @Override
     public Set<GroupModel> getGroups() {
-        if (user.getGroupIds() == null && user.getGroupIds().size() == 0) return Collections.EMPTY_SET;
+        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));

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>

pom.xml 20(+5 -15)

diff --git a/pom.xml b/pom.xml
index 12a346e..d545efd 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 -->
@@ -599,11 +599,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>
@@ -838,7 +833,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 +956,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 +1431,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 +1452,7 @@
                                     <requireMavenVersion>
                                         <version>3.1.1</version>
                                     </requireMavenVersion>
-                                </rules>    
+                                </rules>
                             </configuration>
                         </execution>
                     </executions>
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-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..298623d
--- /dev/null
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java
@@ -0,0 +1,79 @@
+package org.keycloak.protocol.saml.clientregistration;
+
+import org.jboss.logging.Logger;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.events.EventType;
+import org.keycloak.exportimport.ClientDescriptionConverter;
+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.protocol.saml.EntityDescriptorDescriptionConverter;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.services.ErrorResponse;
+import org.keycloak.services.clientregistration.ClientRegAuth;
+import org.keycloak.services.clientregistration.ClientRegistrationProvider;
+
+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 implements ClientRegistrationProvider {
+
+    private static final Logger logger = Logger.getLogger(EntityDescriptorClientRegistrationProvider.class);
+
+    private KeycloakSession session;
+    private EventBuilder event;
+    private ClientRegAuth auth;
+
+    public EntityDescriptorClientRegistrationProvider(KeycloakSession session) {
+        this.session = session;
+    }
+
+//    @POST
+//    @Consumes(MediaType.APPLICATION_XML)
+//    @Produces(MediaType.APPLICATION_JSON)
+//    public Response create(String descriptor) {
+//        event.event(EventType.CLIENT_REGISTER);
+//
+//        auth.requireCreate();
+//
+//        ClientRepresentation client = session.getProvider(ClientDescriptionConverter.class, EntityDescriptorDescriptionConverter.ID).convertToInternal(descriptor);
+//
+//        try {
+//            ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), 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");
+//        }
+//    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public void setAuth(ClientRegAuth auth) {
+        this.auth = auth;
+    }
+
+    @Override
+    public void setEvent(EventBuilder event) {
+        this.event = event;
+    }
+
+}
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/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/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java
index ffb2300..b4ee957 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java
@@ -10,11 +10,12 @@ 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.models.utils.FormMessage;
 import org.keycloak.services.messages.Messages;
 
 /**
@@ -78,6 +79,15 @@ public class IdpCreateUserIfUniqueAuthenticator extends AbstractIdpAuthenticator
                     .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);
+            }
         }
     }
 
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
index d6bf10f..ae28d3e 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java
@@ -14,6 +14,10 @@ import org.keycloak.authentication.authenticators.broker.util.SerializedBrokered
 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;
@@ -52,6 +56,15 @@ public class IdpEmailVerificationAuthenticator extends AbstractIdpAuthenticator 
         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 {
 
@@ -60,15 +73,11 @@ public class IdpEmailVerificationAuthenticator extends AbstractIdpAuthenticator 
                     .setUser(existingUser)
                     .setAttribute(EmailProvider.IDENTITY_PROVIDER_BROKER_CONTEXT, brokerContext)
                     .sendConfirmIdentityBrokerLink(link, expiration);
-//            event.clone().event(EventType.SEND_RESET_PASSWORD)
-//                    .user(user)
-//                    .detail(Details.USERNAME, username)
-//                    .detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, context.getClientSession().getId()).success();
+
+            event.success();
         } catch (EmailException e) {
-//            event.clone().event(EventType.SEND_RESET_PASSWORD)
-//                    .detail(Details.USERNAME, username)
-//                    .user(user)
-//                    .error(Errors.EMAIL_SEND_FAILED);
+            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)
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
index b293d1b..5e732d8 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUsernamePasswordForm.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUsernamePasswordForm.java
@@ -37,7 +37,7 @@ public class IdpUsernamePasswordForm extends UsernamePasswordForm {
         // Restore formData for the case of error
         setupForm(context, formData, existingUser);
 
-        return validatePassword(context, formData);
+        return validatePassword(context, existingUser, formData);
     }
 
     protected LoginFormsProvider setupForm(AuthenticationFlowContext context, MultivaluedMap<String, String> formData, UserModel existingUser) {
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..0506f21 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,29 +145,27 @@ 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();
+            invalidPassword(context, user);
             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().error(Errors.INVALID_USER_CREDENTIALS);
-            Response challengeResponse = invalidCredentials(context);
-            context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challengeResponse);
-            context.clearUser();
+            invalidPassword(context, user);
             return false;
         }
         return true;
     }
+
+    private void invalidPassword(AuthenticationFlowContext context, UserModel user) {
+        context.getEvent().user(user);
+        context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
+        Response challengeResponse = invalidCredentials(context);
+        context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challengeResponse);
+        context.clearUser();
+    }
 }
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/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/OIDCClientDescriptionConverter.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCClientDescriptionConverter.java
index 56b47bb..955dfe4 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCClientDescriptionConverter.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCClientDescriptionConverter.java
@@ -8,6 +8,7 @@ import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.oidc.representations.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 +18,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 +29,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 +55,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/representations/OIDCClientRepresentation.java b/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCClientRepresentation.java
index 7de415c..4a83ebd 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCClientRepresentation.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCClientRepresentation.java
@@ -12,12 +12,48 @@ public class OIDCClientRepresentation {
     @JsonProperty("redirect_uris")
     private List<String> redirectUris;
 
+    @JsonProperty("token_endpoint_auth_method")
+    private String tokenEndpointAuthMethod;
+
+    @JsonProperty("grant_types")
+    private String grantTypes;
+
+    @JsonProperty("response_types")
+    private String responseTypes;
+
     @JsonProperty("client_name")
     private String clientName;
 
     @JsonProperty("client_uri")
     private String clientUri;
 
+    @JsonProperty("logo_uri")
+    private String logoUri;
+
+    @JsonProperty("scope")
+    private String scope;
+
+    @JsonProperty("contacts")
+    private String contacts;
+
+    @JsonProperty("tos_uri")
+    private String tos_uri;
+
+    @JsonProperty("policy_uri")
+    private String policy_uri;
+
+    @JsonProperty("jwks_uri")
+    private String jwks_uri;
+
+    @JsonProperty("jwks")
+    private String jwks;
+
+    @JsonProperty("software_id")
+    private String softwareId;
+
+    @JsonProperty("software_version")
+    private String softwareVersion;
+
     public List<String> getRedirectUris() {
         return redirectUris;
     }
@@ -26,6 +62,30 @@ public class OIDCClientRepresentation {
         this.redirectUris = redirectUris;
     }
 
+    public String getTokenEndpointAuthMethod() {
+        return tokenEndpointAuthMethod;
+    }
+
+    public void setTokenEndpointAuthMethod(String tokenEndpointAuthMethod) {
+        this.tokenEndpointAuthMethod = tokenEndpointAuthMethod;
+    }
+
+    public String getGrantTypes() {
+        return grantTypes;
+    }
+
+    public void setGrantTypes(String grantTypes) {
+        this.grantTypes = grantTypes;
+    }
+
+    public String getResponseTypes() {
+        return responseTypes;
+    }
+
+    public void setResponseTypes(String responseTypes) {
+        this.responseTypes = responseTypes;
+    }
+
     public String getClientName() {
         return clientName;
     }
@@ -42,4 +102,76 @@ public class OIDCClientRepresentation {
         this.clientUri = clientUri;
     }
 
+    public String getLogoUri() {
+        return logoUri;
+    }
+
+    public void setLogoUri(String logoUri) {
+        this.logoUri = logoUri;
+    }
+
+    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 getTos_uri() {
+        return tos_uri;
+    }
+
+    public void setTos_uri(String tos_uri) {
+        this.tos_uri = tos_uri;
+    }
+
+    public String getPolicy_uri() {
+        return policy_uri;
+    }
+
+    public void setPolicy_uri(String policy_uri) {
+        this.policy_uri = policy_uri;
+    }
+
+    public String getJwks_uri() {
+        return jwks_uri;
+    }
+
+    public void setJwks_uri(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 softwareId;
+    }
+
+    public void setSoftwareId(String softwareId) {
+        this.softwareId = softwareId;
+    }
+
+    public String getSoftwareVersion() {
+        return softwareVersion;
+    }
+
+    public void setSoftwareVersion(String softwareVersion) {
+        this.softwareVersion = softwareVersion;
+    }
+
 }
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/AdapterInstallationClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/AdapterInstallationClientRegistrationProvider.java
new file mode 100644
index 0000000..feffc5f
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/clientregistration/AdapterInstallationClientRegistrationProvider.java
@@ -0,0 +1,92 @@
+package org.keycloak.services.clientregistration;
+
+import org.jboss.resteasy.spi.UnauthorizedException;
+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 ClientRegAuth 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(ClientRegAuth 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/ClientRegAuth.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegAuth.java
new file mode 100644
index 0000000..4964349
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegAuth.java
@@ -0,0 +1,128 @@
+package org.keycloak.services.clientregistration;
+
+import org.jboss.resteasy.spi.UnauthorizedException;
+import org.keycloak.events.Errors;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.models.*;
+import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.services.ForbiddenException;
+import org.keycloak.services.managers.AppAuthManager;
+import org.keycloak.services.managers.AuthenticationManager;
+
+import javax.ws.rs.core.HttpHeaders;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientRegAuth {
+
+    private KeycloakSession session;
+    private EventBuilder event;
+
+    private String token;
+    private AccessToken.Access bearerRealmAccess;
+
+    private boolean authenticated = false;
+
+    public ClientRegAuth(KeycloakSession session, EventBuilder event) {
+        this.session = session;
+        this.event = event;
+
+        init();
+    }
+
+    private void init() {
+        RealmModel realm = session.getContext().getRealm();
+
+        String authorizationHeader = session.getContext().getRequestHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
+        if (authorizationHeader == null) {
+            return;
+        }
+
+        String[] split = authorizationHeader.split(" ");
+        if (!split[0].equalsIgnoreCase("bearer")) {
+            return;
+        }
+
+        if (split[1].indexOf('.') == -1) {
+            token = split[1];
+            authenticated = true;
+        } else {
+            AuthenticationManager.AuthResult authResult = new AppAuthManager().authenticateBearerToken(session, realm);
+            bearerRealmAccess = authResult.getToken().getResourceAccess(Constants.REALM_MANAGEMENT_CLIENT_ID);
+            authenticated = true;
+        }
+    }
+
+    public boolean isAuthenticated() {
+        return authenticated;
+    }
+
+    public void requireCreate() {
+        if (!authenticated) {
+            event.error(Errors.NOT_ALLOWED);
+            throw new UnauthorizedException();
+        }
+
+        if (bearerRealmAccess != null) {
+            if (bearerRealmAccess.isUserInRole(AdminRoles.MANAGE_CLIENTS) || bearerRealmAccess.isUserInRole(AdminRoles.CREATE_CLIENT)) {
+                return;
+            }
+        }
+
+        event.error(Errors.NOT_ALLOWED);
+        throw new ForbiddenException();
+    }
+
+    public void requireView(ClientModel client) {
+        if (!authenticated) {
+            event.error(Errors.NOT_ALLOWED);
+            throw new UnauthorizedException();
+        }
+
+        if (client == null) {
+            event.error(Errors.NOT_ALLOWED);
+            throw new ForbiddenException();
+        }
+
+        if (bearerRealmAccess != null) {
+            if (bearerRealmAccess.isUserInRole(AdminRoles.MANAGE_CLIENTS) || bearerRealmAccess.isUserInRole(AdminRoles.VIEW_CLIENTS)) {
+                return;
+            }
+        } else if (token != null) {
+            if (client.getRegistrationSecret() != null && client.getRegistrationSecret().equals(token)) {
+                return;
+            }
+        }
+
+        event.error(Errors.NOT_ALLOWED);
+        throw new ForbiddenException();
+    }
+
+    public void requireUpdate(ClientModel client) {
+        if (!authenticated) {
+            event.error(Errors.NOT_ALLOWED);
+            throw new UnauthorizedException();
+        }
+
+        if (client == null) {
+            event.error(Errors.NOT_ALLOWED);
+            throw new ForbiddenException();
+        }
+
+        if (bearerRealmAccess != null) {
+            if (bearerRealmAccess.isUserInRole(AdminRoles.MANAGE_CLIENTS) || bearerRealmAccess.isUserInRole(AdminRoles.VIEW_CLIENTS)) {
+                return;
+            }
+        } else if (token != null) {
+            if (client.getRegistrationSecret() != null && client.getRegistrationSecret().equals(token)) {
+                return;
+            }
+        }
+
+        event.error(Errors.NOT_ALLOWED);
+        throw new ForbiddenException();
+    }
+
+}
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..0c6bc4e 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationProvider.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationProvider.java
@@ -9,7 +9,7 @@ import org.keycloak.provider.Provider;
  */
 public interface ClientRegistrationProvider extends Provider {
 
-    void setRealm(RealmModel realm);
+    void setAuth(ClientRegAuth 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..ea508de 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java
@@ -2,36 +2,43 @@ package org.keycloak.services.clientregistration;
 
 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) {
+        checkSsl();
+
         ClientRegistrationProvider provider = session.getProvider(ClientRegistrationProvider.class, providerId);
-        provider.setRealm(realm);
         provider.setEvent(event);
+        provider.setAuth(new ClientRegAuth(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/DefaultClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java
index 04cb46a..603ed08 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,16 @@
 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.ModelDuplicateException;
 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;
@@ -26,31 +20,29 @@ import java.net.URI;
  */
 public class DefaultClientRegistrationProvider implements ClientRegistrationProvider {
 
-    private static final Logger logger = Logger.getLogger(DefaultClientRegistrationProvider.class);
-
     private KeycloakSession session;
     private EventBuilder event;
-    private RealmModel realm;
+    private ClientRegAuth auth;
 
     public DefaultClientRegistrationProvider(KeycloakSession session) {
         this.session = session;
     }
 
-
     @POST
     @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
     public Response create(ClientRepresentation client) {
         event.event(EventType.CLIENT_REGISTER);
 
-        authenticate(true, null);
+        auth.requireCreate();
 
         try {
-            ClientModel clientModel = RepresentationToModel.createClient(session, realm, client, true);
+            ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true);
+            clientModel.setRegistrationSecret(TokenGenerator.createRegistrationAccessToken());
+
             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) {
@@ -64,10 +56,10 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv
     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();
-        }
+        ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
+        auth.requireView(client);
+
+        event.client(client.getClientId()).success();
         return Response.ok(ModelToRepresentation.toRepresentation(client)).build();
     }
 
@@ -77,12 +69,12 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv
     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);
+        ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
+        auth.requireUpdate(client);
 
-        logger.infov("Updated client {0}", rep.getClientId());
+        RepresentationToModel.updateClient(rep, client);
 
-        event.success();
+        event.client(client.getClientId()).success();
         return Response.status(Response.Status.OK).build();
     }
 
@@ -91,9 +83,11 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv
     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();
+        ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
+        auth.requireUpdate(client);
+
+        if (session.getContext().getRealm().removeClient(client.getId())) {
+            event.client(client.getClientId()).success();
             return Response.ok().build();
         } else {
             return Response.status(Response.Status.NOT_FOUND).build();
@@ -101,55 +95,17 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv
     }
 
     @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(ClientRegAuth 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/oidc/DescriptionConverter.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java
new file mode 100644
index 0000000..1e0784c
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java
@@ -0,0 +1,38 @@
+package org.keycloak.services.clientregistration.oidc;
+
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.protocol.oidc.representations.OIDCClientRepresentation;
+import org.keycloak.representations.idm.ClientRepresentation;
+
+/**
+ * @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(KeycloakModelUtils.generateId());
+        client.setName(clientOIDC.getClientName());
+        client.setRedirectUris(clientOIDC.getRedirectUris());
+        client.setBaseUrl(clientOIDC.getClientUri());
+        return client;
+    }
+
+    public static OIDCClientResponseRepresentation toExternalResponse(ClientRepresentation client) {
+        OIDCClientResponseRepresentation response = new OIDCClientResponseRepresentation();
+        response.setClientId(client.getClientId());
+
+        response.setClientName(client.getName());
+        response.setClientUri(client.getBaseUrl());
+
+        response.setClientSecret(client.getSecret());
+        response.setClientSecretExpiresAt(0);
+
+        response.setRedirectUris(client.getRedirectUris());
+
+        response.setRegistrationAccessToken(client.getRegistrationAccessToken());
+
+        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..4d131cc
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java
@@ -0,0 +1,100 @@
+package org.keycloak.services.clientregistration.oidc;
+
+import org.jboss.logging.Logger;
+import org.keycloak.common.util.Time;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.events.EventType;
+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.protocol.oidc.OIDCClientDescriptionConverter;
+import org.keycloak.protocol.oidc.representations.OIDCClientRepresentation;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.services.ErrorResponse;
+import org.keycloak.services.clientregistration.ClientRegAuth;
+import org.keycloak.services.clientregistration.ClientRegistrationProvider;
+import org.keycloak.services.clientregistration.TokenGenerator;
+
+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 OIDCClientRegistrationProvider implements ClientRegistrationProvider {
+
+    private static final Logger logger = Logger.getLogger(OIDCClientRegistrationProvider.class);
+
+    private KeycloakSession session;
+    private EventBuilder event;
+    private ClientRegAuth auth;
+
+    public OIDCClientRegistrationProvider(KeycloakSession session) {
+        this.session = session;
+    }
+
+//    @POST
+//    @Consumes(MediaType.APPLICATION_JSON)
+//    @Produces(MediaType.APPLICATION_JSON)
+//    public Response create(OIDCClientRepresentation clientOIDC) {
+//        event.event(EventType.CLIENT_REGISTER);
+//
+//        auth.requireCreate();
+//
+//        ClientRepresentation client = DescriptionConverter.toInternal(clientOIDC);
+//
+//        try {
+//            ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true);
+//
+//            client = ModelToRepresentation.toRepresentation(clientModel);
+//
+//            String registrationAccessToken = TokenGenerator.createRegistrationAccessToken();
+//
+//            clientModel.setRegistrationSecret(registrationAccessToken);
+//
+//            URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build();
+//
+//            logger.infov("Created client {0}", client.getClientId());
+//
+//            event.client(client.getClientId()).success();
+//
+//            OIDCClientResponseRepresentation response = DescriptionConverter.toExternalResponse(client);
+//
+//            response.setClientName(client.getName());
+//            response.setClientUri(client.getBaseUrl());
+//
+//            response.setClientSecret(client.getSecret());
+//            response.setClientSecretExpiresAt(0);
+//
+//            response.setRedirectUris(client.getRedirectUris());
+//
+//            response.setRegistrationAccessToken(registrationAccessToken);
+//            response.setRegistrationClientUri(uri.toString());
+//
+//            return Response.created(uri).entity(response).build();
+//        } catch (ModelDuplicateException e) {
+//            return ErrorResponse.exists("Client " + client.getClientId() + " already exists");
+//        }
+//    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public void setAuth(ClientRegAuth auth) {
+        this.auth = auth;
+    }
+
+    @Override
+    public void setEvent(EventBuilder event) {
+        this.event = event;
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientResponseRepresentation.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientResponseRepresentation.java
new file mode 100644
index 0000000..cd4eea4
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientResponseRepresentation.java
@@ -0,0 +1,77 @@
+package org.keycloak.services.clientregistration.oidc;
+
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.keycloak.protocol.oidc.representations.OIDCClientRepresentation;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class OIDCClientResponseRepresentation extends OIDCClientRepresentation {
+
+    @JsonProperty("client_id")
+    private String clientId;
+
+    @JsonProperty("client_secret")
+    private String clientSecret;
+
+    @JsonProperty("client_id_issued_at")
+    private int clientIdIssuedAt;
+
+    @JsonProperty("client_secret_expires_at")
+    private int clientSecretExpiresAt;
+
+    @JsonProperty("registration_client_uri")
+    private String registrationClientUri;
+
+    @JsonProperty("registration_access_token")
+    private String registrationAccessToken;
+
+    public String getClientId() {
+        return clientId;
+    }
+
+    public void setClientId(String clientId) {
+        this.clientId = clientId;
+    }
+
+    public String getClientSecret() {
+        return clientSecret;
+    }
+
+    public void setClientSecret(String clientSecret) {
+        this.clientSecret = clientSecret;
+    }
+
+    public int getClientIdIssuedAt() {
+        return clientIdIssuedAt;
+    }
+
+    public void setClientIdIssuedAt(int clientIdIssuedAt) {
+        this.clientIdIssuedAt = clientIdIssuedAt;
+    }
+
+    public int getClientSecretExpiresAt() {
+        return clientSecretExpiresAt;
+    }
+
+    public void setClientSecretExpiresAt(int clientSecretExpiresAt) {
+        this.clientSecretExpiresAt = clientSecretExpiresAt;
+    }
+
+    public String getRegistrationClientUri() {
+        return registrationClientUri;
+    }
+
+    public void setRegistrationClientUri(String registrationClientUri) {
+        this.registrationClientUri = registrationClientUri;
+    }
+
+    public String getRegistrationAccessToken() {
+        return registrationAccessToken;
+    }
+
+    public void setRegistrationAccessToken(String registrationAccessToken) {
+        this.registrationAccessToken = registrationAccessToken;
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/TokenGenerator.java b/services/src/main/java/org/keycloak/services/clientregistration/TokenGenerator.java
new file mode 100644
index 0000000..3c49d0f
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/clientregistration/TokenGenerator.java
@@ -0,0 +1,27 @@
+package org.keycloak.services.clientregistration;
+
+import org.keycloak.common.util.Base64Url;
+
+import java.security.SecureRandom;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class TokenGenerator {
+
+    private static final int REGISTRATION_ACCESS_TOKEN_BYTES = 32;
+
+    private TokenGenerator() {
+    }
+
+    public String createInitialAccessToken() {
+        return null;
+    }
+
+    public static String createRegistrationAccessToken() {
+        byte[] buf = new byte[REGISTRATION_ACCESS_TOKEN_BYTES];
+        new SecureRandom().nextBytes(buf);
+        return Base64Url.encode(buf);
+    }
+
+}
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/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index 71f849b..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);
@@ -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/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
index 5d76778..17a16f5 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
@@ -50,6 +50,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 +105,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);;
             }
 
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
index 6c973e1..87253c1 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java
@@ -10,6 +10,7 @@ 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;
@@ -19,12 +20,15 @@ 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;
@@ -256,6 +260,42 @@ public class GroupResource {
 
     }
 
+    /**
+     * Get users
+     *
+     * Returns a list of users, filtered according to query parameters
+     *
+     * @param firstResult Pagination offset
+     * @param maxResults Pagination size
+     * @return
+     */
+    @GET
+    @NoCache
+    @Path("{id}/members")
+    @Produces(MediaType.APPLICATION_JSON)
+    public List<UserRepresentation> getMembers(@PathParam("id") String id,
+                                               @QueryParam("first") Integer firstResult,
+                                               @QueryParam("max") Integer maxResults) {
+        auth.requireView();
+
+        GroupModel group = session.realms().getGroupById(id, realm);
+        if (group == null) {
+            throw new NotFoundException("Group not found");
+        }
+
+        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/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index 9ba114c..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
@@ -17,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;
@@ -36,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;
@@ -676,7 +678,7 @@ public class UsersResource {
 
     }
 
-    /**
+     /**
      * Set up a temporary password for the user
      *
      * User will have to reset the temporary password next time they log in.
@@ -911,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/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 fda37ef..5912536 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -65,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;
@@ -74,6 +75,7 @@ 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;
@@ -368,6 +370,13 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
                     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)) {
 
@@ -388,15 +397,17 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
                     federatedUser.setEmailVerified(true);
                 }
 
-                this.event.clone().user(federatedUser).event(EventType.REGISTER)
-                        .detail(Details.IDENTITY_PROVIDER, providerId)
-                        .detail(Details.IDENTITY_PROVIDER_USERNAME, context.getUsername())
-                        .removeDetail("auth_method")
+                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);
             }
 
@@ -410,7 +421,6 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
                 return finishBrokerAuthentication(context, federatedUser, clientSession, providerId);
             }
         }  catch (Exception e) {
-            // TODO?
             return redirectToErrorPage(Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, e);
         }
     }
@@ -453,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();
@@ -466,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();
     }
 
@@ -568,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();
 
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 8f845c0..7f15d2d 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -509,6 +509,10 @@ public class LoginActionsService {
         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
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..4b5e4f2 100755
--- a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
@@ -113,11 +113,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/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/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 8af43b2..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
@@ -42,6 +42,7 @@ 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;
@@ -87,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();
@@ -99,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
@@ -123,7 +124,7 @@ public abstract class AbstractIdentityProviderTest {
     @WebResource
     protected AccountFederatedIdentityPage accountFederatedIdentityPage;
 
-    private KeycloakSession session;
+    protected KeycloakSession session;
 
     @Before
     public void onBefore() {
@@ -140,87 +141,19 @@ public abstract class AbstractIdentityProviderTest {
         brokerServerRule.stopSession(this.session, true);
     }
 
-    @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(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);
 
@@ -245,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();
-            setUpdateProfileFirstLogin(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);
-        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(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);
-        }
-    }
 
     protected void doAssertFederatedUserNoEmail(UserModel federatedUser) {
         assertEquals("kc-oidc-idp.test-user-noemail", federatedUser.getUsername());
@@ -413,316 +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());
-     }
-
-    // TODO: Reenable and adjust to KEYCLOAK-1750 changed behaviour
-    // @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);
-    }
-
-    // TODO: Reenable and adjust to KEYCLOAK-1750 changed behaviour
-    // @Test
-    public void testUserAlreadyExistsWhenNotUpdatingProfile() {
-        IdentityProviderModel identityProviderModel = getIdentityProviderModel();
-
-        setUpdateProfileFirstLogin(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();
-        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();
-    }
-
-    @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());
-
-        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);
 
 
@@ -738,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"));
@@ -776,6 +241,7 @@ public abstract class AbstractIdentityProviderTest {
 
     protected abstract String getProviderId();
 
+
     protected IdentityProviderModel getIdentityProviderModel() {
         IdentityProviderModel identityProviderModel = getRealm().getIdentityProviderByAlias(getProviderId());
 
@@ -786,10 +252,16 @@ public abstract class AbstractIdentityProviderTest {
         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";
@@ -805,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();
@@ -835,40 +294,60 @@ 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;
     }
-
-    private void setUpdateProfileFirstLogin(final String updateProfileFirstLogin) {
-        KeycloakModelUtils.runJobInTransaction(this.session.getKeycloakSessionFactory(), new KeycloakSessionTask() {
-
-            @Override
-            public void run(KeycloakSession session) {
-                RealmModel realm = session.realms().getRealm("realm-with-broker");
-                AuthenticatorConfigModel reviewProfileConfig = realm.getAuthenticatorConfigByAlias(DefaultAuthenticationFlows.IDP_REVIEW_PROFILE_CONFIG_ALIAS);
-                reviewProfileConfig.getConfig().put(IdpReviewProfileAuthenticatorFactory.UPDATE_PROFILE_ON_FIRST_LOGIN, updateProfileFirstLogin);
-                realm.updateAuthenticatorConfig(reviewProfileConfig);
-            }
-
-        });
-    }
 }
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/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/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/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/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-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/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/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/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/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..8b8dfad
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java
@@ -0,0 +1,96 @@
+package org.keycloak.testsuite.client;
+
+import org.junit.After;
+import org.junit.Before;
+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 javax.ws.rs.core.Response;
+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 = new ClientRegistration(testContext.getAuthServerContextRoot() + "/auth", "test");
+    }
+
+    @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) {
+        Response response = adminClient.realm(REALM_NAME).clients().create(client);
+        String id = response.getLocation().toString();
+        id = id.substring(id.lastIndexOf('/') + 1);
+        client.setId(id);
+        response.close();
+        return client;
+    }
+
+    public ClientRepresentation getClient(String clientId) {
+        try {
+            return adminClient.realm(REALM_NAME).clients().get(clientId).toRepresentation();
+        } catch (NotFoundException e) {
+            return null;
+        }
+    }
+
+}
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..b8bdb41
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AdapterInstallationConfigTest.java
@@ -0,0 +1,103 @@
+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.adapters.config.AdapterConfig;
+import org.keycloak.representations.idm.ClientRepresentation;
+
+import javax.ws.rs.core.Response;
+
+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;
+
+    @Before
+    public void before() throws Exception {
+        super.before();
+
+        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);
+
+        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);
+    }
+
+    @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);
+    }
+
+}
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..8b84b10 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,302 +1,211 @@
 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 void authCreateClients() {
+        reg.auth(Auth.token(getToken("create-clients", "password")));
+    }
+
+    private void authManageClients() {
+        reg.auth(Auth.token(getToken("manage-clients", "password")));
     }
 
-    private ClientRegistration.ClientRegistrationBuilder clientBuilder() {
-        return ClientRegistration.create().realm("test").authServerUrl(testContext.getAuthServerContextRoot() + "/auth");
+    private void authNoAccess() {
+        reg.auth(Auth.token(getToken("no-access", "password")));
     }
 
     private String getToken(String username, String password) {
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..d76da19
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/RegistrationAccessTokenTest.java
@@ -0,0 +1,94 @@
+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 javax.ws.rs.core.Response;
+
+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();
+
+        client = new ClientRepresentation();
+        client.setEnabled(true);
+        client.setClientId("RegistrationAccessTokenTest");
+        client.setSecret("RegistrationAccessTokenTestClientSecret");
+        client.setRegistrationAccessToken("RegistrationAccessTokenTestRegistrationAccessToken");
+        client.setRootUrl("http://root");
+        client = createClient(client);
+
+        reg.auth(Auth.token(client.getRegistrationAccessToken()));
+    }
+
+    @Test
+    public void getClientWithRegistrationToken() throws ClientRegistrationException {
+        ClientRepresentation rep = reg.get(client.getClientId());
+        assertNotNull(rep);
+    }
+
+    @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");
+        reg.update(client);
+
+        assertEquals("http://newroot", getClient(client.getId()).getRootUrl());
+    }
+
+    @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/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/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