/*
 * 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.broker;

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.KeycloakSession;
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.storage.UserStorageProviderModel;
import org.keycloak.testsuite.broker.util.UserSessionStatusServlet;
import org.keycloak.testsuite.federation.DummyUserFederationProviderFactory;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;

import javax.mail.MessagingException;
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 java.io.IOException;
import java.net.URI;
import java.util.Set;

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 testDisabledUser() throws Exception {
        KeycloakSession session = brokerServerRule.startSession();
        setUpdateProfileFirstLogin(session.realms().getRealmByName("realm-with-broker"), IdentityProviderRepresentation.UPFLM_OFF);
        brokerServerRule.stopSession(session, true);

        driver.navigate().to("http://localhost:8081/test-app");
        loginPage.clickSocial(getProviderId());
        loginPage.login("test-user", "password");
        System.out.println(driver.getPageSource());
        driver.navigate().to("http://localhost:8081/test-app/logout");

        try {
            session = brokerServerRule.startSession();
            session.users().getUserByUsername("test-user", session.realms().getRealmByName("realm-with-broker")).setEnabled(false);
            brokerServerRule.stopSession(session, true);

            driver.navigate().to("http://localhost:8081/test-app");

            loginPage.clickSocial(getProviderId());
            loginPage.login("test-user", "password");

            assertTrue(errorPage.isCurrent());
            assertEquals("Account is disabled, contact admin.", errorPage.getError());
        } finally {
            session = brokerServerRule.startSession();
            session.users().getUserByUsername("test-user", session.realms().getRealmByName("realm-with-broker")).setEnabled(true);
            brokerServerRule.stopSession(session, true);
        }
    }

    @Test
    public void testTemporarilyDisabledUser() {
        KeycloakSession session = brokerServerRule.startSession();
        setUpdateProfileFirstLogin(session.realms().getRealmByName("realm-with-broker"), IdentityProviderRepresentation.UPFLM_OFF);
        brokerServerRule.stopSession(session, true);

        driver.navigate().to("http://localhost:8081/test-app");
        loginPage.clickSocial(getProviderId());
        loginPage.login("test-user", "password");
        driver.navigate().to("http://localhost:8081/test-app/logout");

        try {
            session = brokerServerRule.startSession();
            RealmModel brokerRealm = session.realms().getRealmByName("realm-with-broker");
            brokerRealm.setBruteForceProtected(true);
            brokerRealm.setFailureFactor(2);
            brokerServerRule.stopSession(session, true);

            driver.navigate().to("http://localhost:8081/test-app");
            loginPage.login("test-user", "fail");
            loginPage.login("test-user", "fail");

            driver.navigate().to("http://localhost:8081/test-app");

            assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));

            loginPage.clickSocial(getProviderId());
            loginPage.login("test-user", "password");

            assertTrue(errorPage.isCurrent());
            assertEquals("Account is disabled, contact admin.", errorPage.getError());
        } finally {
            session = brokerServerRule.startSession();
            RealmModel brokerRealm = session.realms().getRealmByName("realm-with-broker");
            brokerRealm.setBruteForceProtected(false);
            brokerRealm.setFailureFactor(0);
            brokerServerRule.stopSession(session, true);
        }
    }

    @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 {
        RealmModel realm = getRealm();
        realm.setVerifyEmail(true);
        setUpdateProfileFirstLogin(realm, IdentityProviderRepresentation.UPFLM_OFF);

        brokerServerRule.stopSession(this.session, true);
        this.session = brokerServerRule.startSession();

        IdentityProviderModel identityProviderModel = getIdentityProviderModel();
        try {
            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.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() throws Exception {
        RealmModel realm = getRealm();
        realm.setVerifyEmail(true);
        setUpdateProfileFirstLogin(realm, IdentityProviderRepresentation.UPFLM_OFF);

        brokerServerRule.stopSession(this.session, true);
        this.session = brokerServerRule.startSession();

        try {
            IdentityProviderModel identityProviderModel = getIdentityProviderModel();

            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() {
        RealmModel realmWithBroker = getRealm();
        realmWithBroker.setVerifyEmail(true);
        setUpdateProfileFirstLogin(realmWithBroker, 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 {
        RealmModel realm = getRealm();
        realm.setVerifyEmail(true);
        setUpdateProfileFirstLogin(realm, IdentityProviderRepresentation.UPFLM_ON);

        brokerServerRule.stopSession(this.session, true);
        this.session = brokerServerRule.startSession();

        IdentityProviderModel identityProviderModel = getIdentityProviderModel();
        try {
            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 for KEYCLOAK-3505 - Verify the claims from the claim set returned by the OIDC UserInfo are correctly mapped
     *  by the user attribute mapper
     *
     */
    protected void verifyAttributeMapperHandlesUserInfoClaims() {
        IdentityProviderModel identityProviderModel = getIdentityProviderModel();
        setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_ON);

        UserModel user = assertSuccessfulAuthentication(identityProviderModel, "test-user", "new@email.com", true);
        Assert.assertEquals("A00", user.getFirstAttribute("tenantid"));
    }

    @Test
    public void testSuccessfulAuthenticationWithoutUpdateProfile_newUser_emailAsUsername() throws Exception {
        RealmModel realm = getRealm();
        realm.setRegistrationEmailAsUsername(true);
        setUpdateProfileFirstLogin(realm, IdentityProviderRepresentation.UPFLM_OFF);

        brokerServerRule.stopSession(this.session, true);
        this.session = brokerServerRule.startSession();

        try {
            IdentityProviderModel identityProviderModel = getIdentityProviderModel();

            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
            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 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-link-" + 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());
        this.loginPage.login("test-user", "password");
        doAfterProviderAuthentication();
        assertTrue(accountFederatedIdentityPage.isCurrent());
        assertTrue(driver.getPageSource().contains("id=\"remove-link-" + identityProviderModel.getAlias() + "\""));

        // Unlink my "test-user"
        accountFederatedIdentityPage.clickRemoveProvider(identityProviderModel.getAlias());
        assertTrue(driver.getPageSource().contains("id=\"add-link-" + identityProviderModel.getAlias() + "\""));

        // Revoke grant in account mgmt
        revokeGrant();

        // 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();
        setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);

        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();

        setStoreToken(identityProviderModel, true);
        try {
            authenticateWithIdentityProvider(identityProviderModel, "test-user", true);

            brokerServerRule.stopSession(session, true);
            session = brokerServerRule.startSession();

            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("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"));
        } finally {
            setStoreToken(identityProviderModel, false);
        }
    }

    private void setStoreToken(IdentityProviderModel identityProviderModel, boolean storeToken) {
        identityProviderModel.setStoreToken(storeToken);
        getRealm().updateIdentityProvider(identityProviderModel);

        brokerServerRule.stopSession(session, storeToken);
        session = brokerServerRule.startSession();
    }

    protected abstract void doAssertTokenRetrieval(String pageSource);

    @Test
    public void testWithLinkedFederationProvider() throws Exception {
        setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);

        // Add federationProvider to realm. It's configured with sync registrations
        RealmModel realm = getRealm();
        UserStorageProviderModel model = new UserStorageProviderModel();
        model.setProviderId(DummyUserFederationProviderFactory.PROVIDER_NAME);
        model.setPriority(1);
        model.setName("test-sync-dummy");
        model.setFullSyncPeriod(-1);
        model.setChangedSyncPeriod(-1);
        model.setLastSync(0);
        UserStorageProviderModel dummyModel = new UserStorageProviderModel(realm.addComponentModel(model));

        brokerServerRule.stopSession(session, true);
        session = brokerServerRule.startSession();

        try {
            // Login as user "test-user" to account management.
            authenticateWithIdentityProvider(getIdentityProviderModel(), "test-user", false);
            changePasswordPage.realm("realm-with-broker");
            changePasswordPage.open();
            assertTrue(changePasswordPage.isCurrent());

            // Assert changing password with old password "secret" as this is the password from federationProvider (See DummyUserFederationProvider)
            changePasswordPage.changePassword("new-password", "new-password");
            Assert.assertEquals("Please specify password.", accountUpdateProfilePage.getError());

            changePasswordPage.changePassword("bad", "new-password", "new-password");
            Assert.assertEquals("Invalid existing password.", accountUpdateProfilePage.getError());

            changePasswordPage.changePassword("secret", "new-password", "new-password");
            Assert.assertEquals("Your password has been updated.", accountUpdateProfilePage.getSuccess());

            // Logout
            driver.navigate().to("http://localhost:8081/test-app/logout");


            // Login as user "test-user-noemail" .
            authenticateWithIdentityProvider(getIdentityProviderModel(), "test-user-noemail", false);
            changePasswordPage.open();
            assertTrue(changePasswordPage.isCurrent());

            //  Assert old password is not required as federationProvider doesn't have it for this user
            changePasswordPage.changePassword("new-password", "new-password");
            Assert.assertEquals("Your password has been updated.", accountUpdateProfilePage.getSuccess());

            // Now it is required as it's set on model
            changePasswordPage.changePassword("new-password2", "new-password2");
            Assert.assertEquals("Please specify password.", accountUpdateProfilePage.getError());

            changePasswordPage.changePassword("new-password", "new-password2", "new-password2");
            Assert.assertEquals("Your password has been updated.", accountUpdateProfilePage.getSuccess());

            // Logout
            driver.navigate().to("http://localhost:8081/test-app/logout");
        } finally {

            // remove dummy federation provider for this realm
            realm = getRealm();
            realm.removeComponent(dummyModel);

            brokerServerRule.stopSession(session, true);
            session = brokerServerRule.startSession();
        }
    }

}
