keycloak-aplcache

KEYCLOAK-5234 (#4595)

10/23/2017 12:13:00 PM

Changes

Details

diff --git a/services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountFederatedIdentityBean.java b/services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountFederatedIdentityBean.java
index 96f4257..193de28 100755
--- a/services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountFederatedIdentityBean.java
+++ b/services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountFederatedIdentityBean.java
@@ -46,7 +46,6 @@ public class AccountFederatedIdentityBean {
 
     public AccountFederatedIdentityBean(KeycloakSession session, RealmModel realm, UserModel user, URI baseUri, String stateChecker) {
         this.session = session;
-        URI accountIdentityUpdateUri = Urls.accountFederatedIdentityUpdate(baseUri, realm.getName());
 
         List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
         Set<FederatedIdentityModel> identities = session.users().getFederatedIdentities(user, realm);
@@ -63,15 +62,8 @@ public class AccountFederatedIdentityBean {
                     availableIdentities++;
                 }
 
-                String action = identity != null ? "remove" : "add";
-                String actionUrl = UriBuilder.fromUri(accountIdentityUpdateUri)
-                        .queryParam("action", action)
-                        .queryParam("provider_id", providerId)
-                        .queryParam("stateChecker", stateChecker)
-                        .build().toString();
-
                 String displayName = KeycloakModelUtils.getIdentityProviderDisplayName(session, provider);
-                FederatedIdentityEntry entry = new FederatedIdentityEntry(identity, displayName, provider.getAlias(), provider.getAlias(), actionUrl,
+                FederatedIdentityEntry entry = new FederatedIdentityEntry(identity, displayName, provider.getAlias(), provider.getAlias(),
                 		  															provider.getConfig() != null ? provider.getConfig().get("guiOrder") : null);
                 orderedSet.add(entry);
             }
@@ -105,17 +97,15 @@ public class AccountFederatedIdentityBean {
         private FederatedIdentityModel federatedIdentityModel;
         private final String providerId;
 		private final String providerName;
-        private final String actionUrl;
         private final String guiOrder;
         private final String displayName;
 
         public FederatedIdentityEntry(FederatedIdentityModel federatedIdentityModel, String displayName, String providerId,
-                                      String providerName, String actionUrl, String guiOrder) {
+                                      String providerName, String guiOrder) {
             this.federatedIdentityModel = federatedIdentityModel;
             this.displayName = displayName;
             this.providerId = providerId;
             this.providerName = providerName;
-            this.actionUrl = actionUrl;
             this.guiOrder = guiOrder;
         }
 
@@ -139,10 +129,6 @@ public class AccountFederatedIdentityBean {
             return federatedIdentityModel != null;
         }
 
-        public String getActionUrl() {
-            return actionUrl;
-        }
-        
         public String getGuiOrder() {
             return guiOrder;
         }
@@ -186,4 +172,4 @@ public class AccountFederatedIdentityBean {
 			return 10000;
 		}
 	}
-}
\ No newline at end of file
+}
diff --git a/services/src/main/java/org/keycloak/forms/account/freemarker/model/UrlBean.java b/services/src/main/java/org/keycloak/forms/account/freemarker/model/UrlBean.java
index 6ba04fe..9ea898b 100755
--- a/services/src/main/java/org/keycloak/forms/account/freemarker/model/UrlBean.java
+++ b/services/src/main/java/org/keycloak/forms/account/freemarker/model/UrlBean.java
@@ -33,7 +33,6 @@ public class UrlBean {
     private URI baseURI;
     private URI baseQueryURI;
     private URI currentURI;
-    private String stateChecker;
 
     public UrlBean(RealmModel realm, Theme theme, URI baseURI, URI baseQueryURI, URI currentURI, String stateChecker) {
         this.realm = realm.getName();
@@ -41,7 +40,6 @@ public class UrlBean {
         this.baseURI = baseURI;
         this.baseQueryURI = baseQueryURI;
         this.currentURI = currentURI;
-        this.stateChecker = stateChecker;
     }
 
     public String getApplicationsUrl() {
@@ -73,7 +71,7 @@ public class UrlBean {
     }
 
     public String getSessionsLogoutUrl() {
-        return Urls.accountSessionsLogoutPage(baseQueryURI, realm, stateChecker).toString();
+        return Urls.accountSessionsLogoutPage(baseQueryURI, realm).toString();
     }
 
     public String getRevokeClientUrl() {
@@ -81,7 +79,7 @@ public class UrlBean {
     }
 
     public String getTotpRemoveUrl() {
-        return Urls.accountTotpRemove(baseQueryURI, realm, stateChecker).toString();
+        return Urls.accountTotpRemove(baseQueryURI, realm).toString();
     }
 
     public String getLogoutUrl() {
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index 0235120..f9eb1e9 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -376,6 +376,11 @@ public class AuthenticationManager {
         return uri.getRawPath();
     }
 
+    public static String getAccountCookiePath(RealmModel realm, UriInfo uriInfo) {
+        URI uri = RealmsResource.accountUrl(uriInfo.getBaseUriBuilder()).build(realm.getName());
+        return uri.getRawPath();
+    }
+
     public static void expireCookie(RealmModel realm, String cookieName, String path, boolean httpOnly, ClientConnection connection) {
         logger.debugv("Expiring cookie: {0} path: {1}", cookieName, path);
         boolean secureOnly = realm.getSslRequired().isRequired(connection);;
diff --git a/services/src/main/java/org/keycloak/services/resources/AbstractSecuredLocalService.java b/services/src/main/java/org/keycloak/services/resources/AbstractSecuredLocalService.java
index cc8abfb..bcc99b6 100755
--- a/services/src/main/java/org/keycloak/services/resources/AbstractSecuredLocalService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AbstractSecuredLocalService.java
@@ -24,14 +24,12 @@ import org.keycloak.OAuth2Constants;
 import org.keycloak.common.ClientConnection;
 import org.keycloak.common.util.Base64Url;
 import org.keycloak.common.util.KeycloakUriBuilder;
-import org.keycloak.common.util.UriUtils;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
 import org.keycloak.services.ForbiddenException;
-import org.keycloak.services.managers.AppAuthManager;
 import org.keycloak.services.managers.Auth;
 import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.util.CookieHelper;
@@ -130,14 +128,20 @@ public abstract class AbstractSecuredLocalService {
     }
 
     protected void updateCsrfChecks() {
-        Cookie cookie = headers.getCookies().get(KEYCLOAK_STATE_CHECKER);
-        if (cookie != null) {
-            stateChecker = cookie.getValue();
-        } else {
+        stateChecker = getStateChecker();
+        if (stateChecker == null) {
             stateChecker = Base64Url.encode(KeycloakModelUtils.generateSecret());
-            String cookiePath = AuthenticationManager.getRealmCookiePath(realm, uriInfo);
+
+            StringBuilder sb = new StringBuilder();
+            sb.append(auth.getSession().getId());
+            sb.append("/");
+            sb.append(stateChecker);
+
+            String sessionCookieValue = sb.toString();
+
+            String cookiePath = AuthenticationManager.getAccountCookiePath(realm, uriInfo);
             boolean secureOnly = realm.getSslRequired().isRequired(clientConnection);
-            CookieHelper.addCookie(KEYCLOAK_STATE_CHECKER, stateChecker, cookiePath, null, null, -1, secureOnly, true);
+            CookieHelper.addCookie(KEYCLOAK_STATE_CHECKER, sessionCookieValue, cookiePath, null, null, -1, secureOnly, true);
         }
     }
 
@@ -149,25 +153,27 @@ public abstract class AbstractSecuredLocalService {
      * @param formData
      */
     protected void csrfCheck(final MultivaluedMap<String, String> formData) {
-        if (!auth.isCookieAuthenticated()) return;
         String stateChecker = formData.getFirst("stateChecker");
-        if (!this.stateChecker.equals(stateChecker)) {
+        if (stateChecker == null || !stateChecker.equals(getStateChecker())) {
             throw new ForbiddenException();
         }
-
     }
 
-    /**
-     * Check to see if form post has sessionId hidden field and match it against the session id.
-     *
-     */
-    protected void csrfCheck(String stateChecker) {
-        if (!auth.isCookieAuthenticated()) return;
-        if (auth.getSession() == null) return;
-        if (!this.stateChecker.equals(stateChecker)) {
-            throw new ForbiddenException();
+    protected String getStateChecker() {
+        Cookie cookie = headers.getCookies().get(KEYCLOAK_STATE_CHECKER);
+        if (cookie != null) {
+            stateChecker = cookie.getValue();
+            String[] s = stateChecker.split("/");
+            if (s.length == 2) {
+                String sessionId = s[0];
+                String stateChecker = s[1];
+
+                if (auth.getSession().getId().equals(sessionId)) {
+                    return stateChecker;
+                }
+            }
         }
-
+        return null;
     }
 
     protected abstract URI getBaseRedirectUri();
@@ -203,32 +209,6 @@ public abstract class AbstractSecuredLocalService {
         return oauth.redirect(uriInfo, accountUri.toString());
     }
 
-    protected Response authenticateBrowser() {
-        AppAuthManager authManager = new AppAuthManager();
-        AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm);
-        if (authResult != null) {
-            auth = new Auth(realm, authResult.getToken(), authResult.getUser(), client, authResult.getSession(), true);
-        } else {
-            return login(null);
-        }
-        // don't allow cors requests
-        // This is to prevent CSRF attacks.
-        String requestOrigin = UriUtils.getOrigin(uriInfo.getBaseUri());
-        String origin = headers.getRequestHeaders().getFirst("Origin");
-        if (origin != null && !requestOrigin.equals(origin)) {
-            throw new ForbiddenException();
-        }
-
-        if (!request.getHttpMethod().equals("GET")) {
-            String referrer = headers.getRequestHeaders().getFirst("Referer");
-            if (referrer != null && !requestOrigin.equals(UriUtils.getOrigin(referrer))) {
-                throw new ForbiddenException();
-            }
-        }
-        updateCsrfChecks();
-        return null;
-    }
-
     static class OAuthRedirect extends AbstractOAuthClient {
 
         /**
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 9b26b18..3a3a680 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -475,15 +475,15 @@ public class AccountService extends AbstractSecuredLocalService {
     }
 
     @Path("totp-remove")
-    @GET
-    public Response processTotpRemove(@QueryParam("stateChecker") String stateChecker) {
+    @POST
+    public Response processTotpRemove(final MultivaluedMap<String, String> formData) {
         if (auth == null) {
             return login("totp");
         }
 
         require(AccountRoles.MANAGE_ACCOUNT);
 
-        csrfCheck(stateChecker);
+        csrfCheck(formData);
 
         UserModel user = auth.getUser();
         session.userCredentialManager().disableCredentialType(realm, user, CredentialModel.OTP);
@@ -496,14 +496,14 @@ public class AccountService extends AbstractSecuredLocalService {
 
 
     @Path("sessions-logout")
-    @GET
-    public Response processSessionsLogout(@QueryParam("stateChecker") String stateChecker) {
+    @POST
+    public Response processSessionsLogout(final MultivaluedMap<String, String> formData) {
         if (auth == null) {
             return login("sessions");
         }
 
         require(AccountRoles.MANAGE_ACCOUNT);
-        csrfCheck(stateChecker);
+        csrfCheck(formData);
 
         UserModel user = auth.getUser();
 
@@ -720,19 +720,21 @@ public class AccountService extends AbstractSecuredLocalService {
         return account.setPasswordSet(true).setSuccess(Messages.ACCOUNT_PASSWORD_UPDATED).createResponse(AccountPages.PASSWORD);
     }
 
-    @Path("federated-identity-update")
-    @GET
-    public Response processFederatedIdentityUpdate(@QueryParam("action") String action,
-                                                   @QueryParam("provider_id") String providerId,
-                                                   @QueryParam("stateChecker") String stateChecker) {
+    @Path("identity")
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response processFederatedIdentityUpdate(final MultivaluedMap<String, String> formData) {
         if (auth == null) {
             return login("identity");
         }
 
         require(AccountRoles.MANAGE_ACCOUNT);
-        csrfCheck(stateChecker);
+        csrfCheck(formData);
         UserModel user = auth.getUser();
 
+        String action = formData.getFirst("action");
+        String providerId = formData.getFirst("providerId");
+
         if (Validation.isEmpty(providerId)) {
             setReferrerOnPage();
             return account.setError(Messages.MISSING_IDENTITY_PROVIDER).createResponse(AccountPages.FEDERATED_IDENTITY);
diff --git a/services/src/main/java/org/keycloak/services/Urls.java b/services/src/main/java/org/keycloak/services/Urls.java
index 51f505e..1d73cf6 100755
--- a/services/src/main/java/org/keycloak/services/Urls.java
+++ b/services/src/main/java/org/keycloak/services/Urls.java
@@ -126,9 +126,8 @@ public class Urls {
         return accountBase(baseUri).path(AccountService.class, "totpPage").build(realmName);
     }
 
-    public static URI accountTotpRemove(URI baseUri, String realmName, String stateChecker) {
+    public static URI accountTotpRemove(URI baseUri, String realmName) {
         return accountBase(baseUri).path(AccountService.class, "processTotpRemove")
-                .queryParam("stateChecker", stateChecker)
                 .build(realmName);
     }
 
@@ -140,9 +139,8 @@ public class Urls {
         return accountBase(baseUri).path(AccountService.class, "sessionsPage").build(realmName);
     }
 
-    public static URI accountSessionsLogoutPage(URI baseUri, String realmName, String stateChecker) {
+    public static URI accountSessionsLogoutPage(URI baseUri, String realmName) {
         return accountBase(baseUri).path(AccountService.class, "processSessionsLogout")
-                .queryParam("stateChecker", stateChecker)
                 .build(realmName);
     }
 
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
index 781714a..fe8d78b 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
@@ -422,7 +422,7 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent
 
         // Assert identity linked in account management
         assertTrue(accountFederatedIdentityPage.isCurrent());
-        assertTrue(driver.getPageSource().contains("id=\"remove-" + identityProviderModel.getAlias() + "\""));
+        assertTrue(driver.getPageSource().contains("id=\"remove-link-" + identityProviderModel.getAlias() + "\""));
 
         // Revoke grant in account mgmt
         revokeGrant();
@@ -436,11 +436,11 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent
         this.loginPage.login("test-user", "password");
         doAfterProviderAuthentication();
         assertTrue(accountFederatedIdentityPage.isCurrent());
-        assertTrue(driver.getPageSource().contains("id=\"remove-" + identityProviderModel.getAlias() + "\""));
+        assertTrue(driver.getPageSource().contains("id=\"remove-link-" + identityProviderModel.getAlias() + "\""));
 
         // Unlink my "test-user"
         accountFederatedIdentityPage.clickRemoveProvider(identityProviderModel.getAlias());
-        assertTrue(driver.getPageSource().contains("id=\"add-" + identityProviderModel.getAlias() + "\""));
+        assertTrue(driver.getPageSource().contains("id=\"add-link-" + identityProviderModel.getAlias() + "\""));
 
         // Revoke grant in account mgmt
         revokeGrant();
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 ac897a8..5b4898b 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
@@ -55,11 +55,11 @@ public class AccountFederatedIdentityPage extends AbstractAccountPage {
     }
 
     public void clickAddProvider(String providerId) {
-        driver.findElement(By.id("add-" + providerId)).click();
+        driver.findElement(By.id("add-link-" + providerId)).click();
     }
 
     public void clickRemoveProvider(String providerId) {
-        driver.findElement(By.id("remove-" + providerId)).click();
+        driver.findElement(By.id("remove-link-" + providerId)).click();
     }
 
     public String getError() {
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountFederatedIdentityPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountFederatedIdentityPage.java
index 9ca2372..7992ccf 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountFederatedIdentityPage.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountFederatedIdentityPage.java
@@ -22,6 +22,9 @@ import org.openqa.selenium.By;
 import org.openqa.selenium.WebElement;
 import org.openqa.selenium.support.FindBy;
 
+import java.util.LinkedList;
+import java.util.List;
+
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
@@ -50,26 +53,72 @@ public class AccountFederatedIdentityPage extends AbstractAccountPage {
     public boolean isCurrent() {
         return driver.getTitle().contains("Account Management") && driver.getPageSource().contains("Federated Identities");
     }
-    
-    public WebElement findAddProviderButton(String alias) {
-        return driver.findElement(By.id("add-" + alias));
+
+    public List<FederatedIdentity> getIdentities() {
+        List<FederatedIdentity> identities = new LinkedList<>();
+        WebElement identitiesElement = driver.findElement(By.id("federated-identities"));
+        for (WebElement i : identitiesElement.findElements(By.className("row"))) {
+
+            String providerId = i.findElement(By.tagName("label")).getText();
+            String subject = i.findElement(By.tagName("input")).getAttribute("value");
+            WebElement button = i.findElement(By.tagName("button"));
+
+            identities.add(new FederatedIdentity(providerId, subject, button));
+        }
+        return identities;
     }
-    
-    public WebElement findRemoveProviderButton(String alias) {
-        return driver.findElement(By.id("remove-" + alias));
+
+    public WebElement findAddProvider(String providerId) {
+        return driver.findElement(By.id("add-link-" + providerId));
     }
 
-    public void clickAddProvider(String alias) {
-        WebElement addButton = findAddProviderButton(alias);
-        addButton.click();
+    public void clickAddProvider(String providerId) {
+        findAddProvider(providerId).click();
     }
 
-    public void clickRemoveProvider(String alias) {
-        WebElement addButton = findRemoveProviderButton(alias);
-        addButton.click();
+    public void clickRemoveProvider(String providerId) {
+        driver.findElement(By.id("remove-link-" + providerId)).click();
     }
 
     public String getError() {
         return errorMessage.getText();
     }
+
+    public static class FederatedIdentity {
+
+        private String providerId;
+        private String subject;
+        private WebElement action;
+
+        public FederatedIdentity(String providerId, String subject, WebElement action) {
+            this.providerId = providerId;
+            this.subject = subject;
+            this.action = action;
+        }
+
+        public String getProvider() {
+            return providerId;
+        }
+
+        public void setProviderId(String providerId) {
+            this.providerId = providerId;
+        }
+
+        public String getSubject() {
+            return subject;
+        }
+
+        public void setSubject(String subject) {
+            this.subject = subject;
+        }
+
+        public WebElement getAction() {
+            return action;
+        }
+
+        public void setAction(WebElement action) {
+            this.action = action;
+        }
+    }
+
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountBrokerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountBrokerTest.java
new file mode 100755
index 0000000..7403073
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountBrokerTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.testsuite.account;
+
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.broker.AbstractBaseBrokerTest;
+import org.keycloak.testsuite.broker.BrokerConfiguration;
+import org.keycloak.testsuite.broker.KcOidcBrokerConfiguration;
+import org.keycloak.testsuite.pages.AccountFederatedIdentityPage;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.util.UserBuilder;
+import org.openqa.selenium.WebElement;
+
+import javax.ws.rs.core.Response;
+import java.util.List;
+
+import static org.keycloak.testsuite.admin.ApiUtil.createUserWithAdminClient;
+import static org.keycloak.testsuite.admin.ApiUtil.resetUserPassword;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
+ */
+public class AccountBrokerTest extends AbstractBaseBrokerTest {
+
+    @Page
+    protected LoginPage loginPage;
+
+    @Page
+    protected AccountFederatedIdentityPage identityPage;
+
+    @Override
+    protected BrokerConfiguration getBrokerConfiguration() {
+        return KcOidcBrokerConfiguration.INSTANCE;
+    }
+
+    @Before
+    public void createUser() {
+        log.debug("creating user for realm " + bc.providerRealmName());
+
+        UserRepresentation user = new UserRepresentation();
+        user.setUsername(bc.getUserLogin());
+        user.setEmail(bc.getUserEmail());
+        user.setEmailVerified(true);
+        user.setEnabled(true);
+
+        RealmResource realmResource = adminClient.realm(bc.providerRealmName());
+        userId = createUserWithAdminClient(realmResource, user);
+
+        resetUserPassword(realmResource.users().get(userId), bc.getUserPassword(), false);
+    }
+
+    @Before
+    public void addIdentityProviderToProviderRealm() {
+        log.debug("adding identity provider to realm " + bc.consumerRealmName());
+
+        RealmResource realm = adminClient.realm(bc.consumerRealmName());
+        realm.identityProviders().create(bc.setUpIdentityProvider(suiteContext)).close();
+        realm.identityProviders().get(bc.getIDPAlias());
+    }
+
+    @Before
+    public void addClients() {
+        List<ClientRepresentation> clients = bc.createProviderClients(suiteContext);
+        if (clients != null) {
+            RealmResource providerRealm = adminClient.realm(bc.providerRealmName());
+            for (ClientRepresentation client : clients) {
+                log.debug("adding client " + client.getName() + " to realm " + bc.providerRealmName());
+
+                providerRealm.clients().create(client).close();
+            }
+        }
+
+        clients = bc.createConsumerClients(suiteContext);
+        if (clients != null) {
+            RealmResource consumerRealm = adminClient.realm(bc.consumerRealmName());
+            for (ClientRepresentation client : clients) {
+                log.debug("adding client " + client.getName() + " to realm " + bc.consumerRealmName());
+
+                consumerRealm.clients().create(client).close();
+            }
+        }
+    }
+
+    @Before
+    public void before() {
+        Response response = adminClient.realm(KcOidcBrokerConfiguration.INSTANCE.consumerRealmName()).users().create(UserBuilder.create().username("accountbrokertest").build());
+        String userId = ApiUtil.getCreatedId(response);
+        ApiUtil.resetUserPassword(adminClient.realm(KcOidcBrokerConfiguration.INSTANCE.consumerRealmName()).users().get(userId), "password", false);
+    }
+
+    @Test
+    public void add() {
+        identityPage.realm(KcOidcBrokerConfiguration.INSTANCE.consumerRealmName());
+        identityPage.open();
+        loginPage.login("accountbrokertest", "password");
+        Assert.assertTrue(identityPage.isCurrent());
+
+        List<AccountFederatedIdentityPage.FederatedIdentity> identities = identityPage.getIdentities();
+        Assert.assertEquals(1, identities.size());
+
+        Assert.assertEquals("kc-oidc-idp", identities.get(0).getProvider());
+        Assert.assertEquals("", identities.get(0).getSubject());
+        Assert.assertEquals("add-link-kc-oidc-idp", identities.get(0).getAction().getAttribute("id"));
+
+        identities.get(0).getAction().click();
+
+        loginPage.login(bc.getUserLogin(), bc.getUserPassword());
+
+        Assert.assertTrue(identityPage.isCurrent());
+
+        identities = identityPage.getIdentities();
+        Assert.assertEquals(1, identities.size());
+
+        Assert.assertEquals("kc-oidc-idp", identities.get(0).getProvider());
+        Assert.assertEquals("user@localhost.com", identities.get(0).getSubject());
+        Assert.assertEquals("remove-link-kc-oidc-idp", identities.get(0).getAction().getAttribute("id"));
+
+        identities.get(0).getAction().click();
+
+        Assert.assertTrue(identityPage.isCurrent());
+
+        identities = identityPage.getIdentities();
+
+        Assert.assertEquals("kc-oidc-idp", identities.get(0).getProvider());
+        Assert.assertEquals("", identities.get(0).getSubject());
+        Assert.assertEquals("add-link-kc-oidc-idp", identities.get(0).getAction().getAttribute("id"));
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java
index c69f489..de2fb90 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java
@@ -983,7 +983,7 @@ public class AccountTest extends AbstractTestRealmKeycloakTest {
     public void testIdentityProviderHiddenOnLoginPageIsVisbleInAccount(){
         federatedIdentityPage.open();
         loginPage.login("test-user@localhost", "password");
-        Assert.assertNotNull(federatedIdentityPage.findAddProviderButton("myhiddenoidc"));
+        Assert.assertNotNull(federatedIdentityPage.findAddProvider("myhiddenoidc"));
     }
 
     @Test
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AccountLinkTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AccountLinkTest.java
index f08b4bf..7c756fa 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AccountLinkTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AccountLinkTest.java
@@ -148,7 +148,7 @@ public class AccountLinkTest extends AbstractKeycloakTest {
 
         // Assert identity linked in account management
         assertTrue(accountFederatedIdentityPage.isCurrent());
-        assertTrue(driver.getPageSource().contains("id=\"remove-" + PARENT_IDP + "\""));
+        assertTrue(driver.getPageSource().contains("id=\"remove-link-" + PARENT_IDP + "\""));
 
         // Logout from account management
         accountFederatedIdentityPage.logout();
@@ -161,11 +161,11 @@ public class AccountLinkTest extends AbstractKeycloakTest {
         System.out.println("--------------------------------");
         System.out.println(driver.getPageSource());
         assertTrue(accountFederatedIdentityPage.isCurrent());
-        assertTrue(driver.getPageSource().contains("id=\"remove-" + PARENT_IDP + "\""));
+        assertTrue(driver.getPageSource().contains("id=\"remove-link-" + PARENT_IDP + "\""));
 
         // Unlink my "test-user"
         accountFederatedIdentityPage.clickRemoveProvider(PARENT_IDP);
-        assertTrue(driver.getPageSource().contains("id=\"add-" + PARENT_IDP + "\""));
+        assertTrue(driver.getPageSource().contains("id=\"add-link-" + PARENT_IDP + "\""));
 
 
         // Logout from account management
diff --git a/themes/src/main/resources/theme/base/account/federatedIdentity.ftl b/themes/src/main/resources/theme/base/account/federatedIdentity.ftl
index 9a90173..a9c6d6c 100755
--- a/themes/src/main/resources/theme/base/account/federatedIdentity.ftl
+++ b/themes/src/main/resources/theme/base/account/federatedIdentity.ftl
@@ -7,26 +7,36 @@
         </div>
     </div>
 
-    <form action="${url.passwordUrl}" class="form-horizontal" method="post">
-        <#list federatedIdentity.identities as identity>
-            <div class="form-group">
-                <div class="col-sm-2 col-md-2">
-                    <label for="${identity.providerId!}" class="control-label">${identity.displayName!}</label>
-                </div>
-                <div class="col-sm-5 col-md-5">
-                    <input disabled="true" class="form-control" value="${identity.userName!}">
-                </div>
-                <div class="col-sm-5 col-md-5">
-                    <#if identity.connected>
-                        <#if federatedIdentity.removeLinkPossible>
-                            <a href="${identity.actionUrl}" type="submit" id="remove-${identity.providerId!}" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}">${msg("doRemove")}</a>
-                        </#if>
-                    <#else>
-                        <a href="${identity.actionUrl}" type="submit" id="add-${identity.providerId!}" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}">${msg("doAdd")}</a>
+    <div id="federated-identities">
+    <#list federatedIdentity.identities as identity>
+        <div class="row margin-bottom">
+            <div class="col-sm-2 col-md-2">
+                <label for="${identity.providerId!}" class="control-label">${identity.displayName!}</label>
+            </div>
+            <div class="col-sm-5 col-md-5">
+                <input disabled="true" class="form-control" value="${identity.userName!}">
+            </div>
+            <div class="col-sm-5 col-md-5">
+                <#if identity.connected>
+                    <#if federatedIdentity.removeLinkPossible>
+                        <form action="${url.socialUrl}" method="post" class="form-inline">
+                            <input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker?html}">
+                            <input type="hidden" id="action" name="action" value="remove">
+                            <input type="hidden" id="providerId" name="providerId" value="${identity.providerId!}">
+                            <button id="remove-link-${identity.providerId!}" class="btn btn-default">${msg("doRemove")}</button>
+                        </form>
                     </#if>
-                </div>
+                <#else>
+                    <form action="${url.socialUrl}" method="post" class="form-inline">
+                        <input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker?html}">
+                        <input type="hidden" id="action" name="action" value="add">
+                        <input type="hidden" id="providerId" name="providerId" value="${identity.providerId!}">
+                        <button id="add-link-${identity.providerId!}" class="btn btn-default">${msg("doAdd")}</button>
+                    </form>
+                </#if>
             </div>
-        </#list>
-    </form>
+        </div>
+    </#list>
+    </div>
 
-</@layout.mainLayout>
\ No newline at end of file
+</@layout.mainLayout>
diff --git a/themes/src/main/resources/theme/base/account/sessions.ftl b/themes/src/main/resources/theme/base/account/sessions.ftl
index 1c0ef1b..5e4441d 100755
--- a/themes/src/main/resources/theme/base/account/sessions.ftl
+++ b/themes/src/main/resources/theme/base/account/sessions.ftl
@@ -36,6 +36,9 @@
 
     </table>
 
-    <a id="logout-all-sessions" href="${url.sessionsLogoutUrl}">${msg("doLogOutAllSessions")}</a>
+    <form action="${url.sessionsLogoutUrl}" method="post">
+        <input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker?html}">
+        <button id="logout-all-sessions" class="btn btn-default">${msg("doLogOutAllSessions")}</button>
+    </form>
 
 </@layout.mainLayout>
diff --git a/themes/src/main/resources/theme/base/account/totp.ftl b/themes/src/main/resources/theme/base/account/totp.ftl
index 4b96dd8..f02ef2c 100755
--- a/themes/src/main/resources/theme/base/account/totp.ftl
+++ b/themes/src/main/resources/theme/base/account/totp.ftl
@@ -14,7 +14,10 @@
         <tr>
             <td class="provider">${msg("mobile")}</td>
             <td class="action">
-                <a id="remove-mobile" href="${url.totpRemoveUrl}"><i class="pficon pficon-delete"></i></a>
+                <form action="${url.totpRemoveUrl}" method="post" class="form-inline">
+                    <input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker?html}">
+                    <button id="remove-mobile" class="btn btn-default"><i class="pficon pficon-delete"></i></button>
+                </form>
                 </td>
             </tr>
         </tbody>
diff --git a/themes/src/main/resources/theme/keycloak/account/resources/css/account.css b/themes/src/main/resources/theme/keycloak/account/resources/css/account.css
index 22edb49..3014bca 100644
--- a/themes/src/main/resources/theme/keycloak/account/resources/css/account.css
+++ b/themes/src/main/resources/theme/keycloak/account/resources/css/account.css
@@ -53,6 +53,10 @@ header .navbar {
     padding: 0 30px;
 }
 
+.margin-bottom {
+    margin-bottom: 10px;
+}
+
 /* Sidebar */
 
 .bs-sidebar {
@@ -262,4 +266,4 @@ hr + .form-horizontal {
 }
 .kc-dropdown:hover ul{
     display:block;
-}
\ No newline at end of file
+}