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

import org.apache.commons.io.IOUtils;
import org.junit.Assert;
import org.junit.rules.ExternalResource;
import org.keycloak.adapters.saml.SamlAuthenticationError;
import org.keycloak.adapters.saml.SamlPrincipal;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.Retry;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.saml.mappers.AttributeStatementHelper;
import org.keycloak.protocol.saml.mappers.GroupMembershipMapper;
import org.keycloak.protocol.saml.mappers.HardcodedAttributeMapper;
import org.keycloak.protocol.saml.mappers.HardcodedRole;
import org.keycloak.protocol.saml.mappers.RoleListMapper;
import org.keycloak.protocol.saml.mappers.RoleNameMapper;
import org.keycloak.protocol.saml.mappers.UserAttributeStatementMapper;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.saml.BaseSAML2BindingBuilder;
import org.keycloak.saml.SAML2ErrorResponseBuilder;
import org.keycloak.saml.SignatureAlgorithm;
import org.keycloak.saml.common.constants.GeneralConstants;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.saml.processing.core.saml.v2.constants.X500SAMLProfileConstants;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.KeycloakServer;
import org.keycloak.testsuite.PageUtils;
import org.keycloak.testsuite.adapter.servlet.SamlSPFacade;
import org.keycloak.testsuite.pages.InputPage;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.rule.AbstractKeycloakRule;
import org.keycloak.testsuite.rule.ErrorServlet;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
import org.openqa.selenium.WebDriver;
import org.w3c.dom.Document;

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Form;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.net.URI;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import static org.junit.Assert.assertEquals;

/**
 * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
 * @version $Revision: 1 $
 */
public class SamlAdapterTestStrategy extends ExternalResource {
    protected String AUTH_SERVER_URL = "http://localhost:8081/auth";
    protected String APP_SERVER_BASE_URL = "http://localhost:8081";
    protected AbstractKeycloakRule keycloakRule;

    private static final String REALM_PRIVATE_KEY_STR = "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=";
    private static PrivateKey REALM_PRIVATE_KEY;
    private static final String REALM_PUBLIC_KEY_STR = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB";
    private static PublicKey REALM_PUBLIC_KEY;

    static {
        try {
            KeyFactory kf = KeyFactory.getInstance("RSA");
            byte[] encoded = Base64.getDecoder().decode(REALM_PUBLIC_KEY_STR);
            REALM_PUBLIC_KEY = (PublicKey) kf.generatePublic(new X509EncodedKeySpec(encoded));

            encoded = Base64.getDecoder().decode(REALM_PRIVATE_KEY_STR);
            REALM_PRIVATE_KEY = (PrivateKey) kf.generatePrivate(new PKCS8EncodedKeySpec(encoded));
        } catch (NoSuchAlgorithmException | InvalidKeySpecException ex) {
            Logger.getLogger(SamlAdapterTestStrategy.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public SamlAdapterTestStrategy(String AUTH_SERVER_URL, String APP_SERVER_BASE_URL, AbstractKeycloakRule keycloakRule) {
        this.AUTH_SERVER_URL = AUTH_SERVER_URL;
        this.APP_SERVER_BASE_URL = APP_SERVER_BASE_URL;
        this.keycloakRule = keycloakRule;
    }

    public WebRule webRule = new WebRule(this);


    @WebResource
    protected WebDriver driver;
    @WebResource
    protected LoginPage loginPage;
    @WebResource
    protected InputPage inputPage;

    @Override
    protected void before() throws Throwable {
        super.before();
        webRule.before();
    }

    @Override
    protected void after() {
        super.after();
        webRule.after();
    }

    public static RealmModel baseAdapterTestInitialization(KeycloakSession session, RealmManager manager, RealmModel adminRealm, Class<?> clazz) {
        RealmRepresentation representation = KeycloakServer.loadJson(clazz.getResourceAsStream("/keycloak-saml/testsaml.json"), RealmRepresentation.class);
        RealmModel demoRealm = manager.importRealm(representation);
        return demoRealm;
    }



    protected void checkLoggedOut(String mainUrl, boolean postBinding) {
        String pageSource = driver.getPageSource();
        System.out.println("*** logout pagesource ***");
        System.out.println(pageSource);
        System.out.println("driver url: " + driver.getCurrentUrl());
        Assert.assertTrue(pageSource.contains("request-path: /logout.jsp"));
        driver.navigate().to(mainUrl);
        checkAtLoginPage(postBinding);
    }

    protected void checkAtLoginPage(boolean postBinding) {
        if (postBinding) assertAtLoginPagePostBinding();
        else assertAtLoginPageRedirectBinding();
    }

    protected void assertAtLoginPageRedirectBinding() {
        Assert.assertTrue(driver.getCurrentUrl().startsWith(AUTH_SERVER_URL + "/realms/demo/protocol/saml"));
    }
    protected void assertAtLoginPagePostBinding() {
        Assert.assertTrue(driver.getCurrentUrl().startsWith(AUTH_SERVER_URL + "/realms/demo/login-actions/authenticate"));
    }

    public void testSavedPostRequest() throws Exception {
        // test login to customer-portal which does a bearer request to customer-db
        driver.navigate().to(APP_SERVER_BASE_URL + "/input-portal");
        System.err.println("*********** Current url: " + driver.getCurrentUrl());
        Assert.assertTrue(driver.getCurrentUrl().startsWith(APP_SERVER_BASE_URL + "/input-portal"));
        inputPage.execute("hello");

        assertAtLoginPagePostBinding();
        loginPage.login("bburke@redhat.com", "password");
        System.out.println("Current url: " + driver.getCurrentUrl());
        Assert.assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/input-portal/secured/post");
        String pageSource = driver.getPageSource();
        System.out.println(pageSource);
        Assert.assertTrue(pageSource.contains("parameter=hello"));
        // test that user principal and KeycloakSecurityContext available
        driver.navigate().to(APP_SERVER_BASE_URL + "/input-portal/insecure");
        System.out.println("insecure: ");
        System.out.println(driver.getPageSource());
        Assert.assertTrue(driver.getPageSource().contains("Insecure Page"));
        if (System.getProperty("insecure.user.principal.unsupported") == null) Assert.assertTrue(driver.getPageSource().contains("UserPrincipal"));

        // test logout

        driver.navigate().to(APP_SERVER_BASE_URL + "/input-portal?GLO=true");

        // test unsecured POST KEYCLOAK-901

        Client client = ClientBuilder.newClient();
        Form form = new Form();
        form.param("parameter", "hello");
        String text = client.target(APP_SERVER_BASE_URL + "/input-portal/unsecured").request().post(Entity.form(form), String.class);
        Assert.assertTrue(text.contains("parameter=hello"));
        client.close();

    }



    public void testErrorHandlingUnsigned() throws Exception {
        ErrorServlet.authError = null;
        Client client = ClientBuilder.newClient();
        // make sure
        Response response = client.target(APP_SERVER_BASE_URL + "/employee-sig/").request().get();
        response.close();
        SAML2ErrorResponseBuilder builder = new SAML2ErrorResponseBuilder()
                .destination(APP_SERVER_BASE_URL + "/employee-sig/saml")
                        .issuer(AUTH_SERVER_URL + "/realms/demo")
                        .status(JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get());
        BaseSAML2BindingBuilder binding = new BaseSAML2BindingBuilder()
                .relayState(null);
        Document document = builder.buildDocument();
        URI uri = binding.redirectBinding(document).generateURI(APP_SERVER_BASE_URL + "/employee-sig/saml", false);
        response = client.target(uri).request().get();
        String errorPage = response.readEntity(String.class);
        response.close();
        Assert.assertTrue(errorPage.contains("Error Page"));
        client.close();
        Assert.assertNotNull(ErrorServlet.authError);
        SamlAuthenticationError error = (SamlAuthenticationError)ErrorServlet.authError;
        Assert.assertEquals(SamlAuthenticationError.Reason.INVALID_SIGNATURE, error.getReason());
        Assert.assertNotNull(error.getStatus());
        ErrorServlet.authError = null;

    }

    public void testErrorHandlingSigned() throws Exception {
        ErrorServlet.authError = null;
        Client client = ClientBuilder.newClient();
        // make sure
        Response response = client.target(APP_SERVER_BASE_URL + "/employee-sig/").request().get();
        response.close();
        SAML2ErrorResponseBuilder builder = new SAML2ErrorResponseBuilder()
                .destination(APP_SERVER_BASE_URL + "/employee-sig/saml")
                        .issuer(AUTH_SERVER_URL + "/realms/demo")
                        .status(JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get());
        BaseSAML2BindingBuilder binding = new BaseSAML2BindingBuilder()
                .relayState(null)
                .signatureAlgorithm(SignatureAlgorithm.RSA_SHA256)
                .signWith(KeyUtils.createKeyId(REALM_PRIVATE_KEY), REALM_PRIVATE_KEY, REALM_PUBLIC_KEY)
                .signDocument();
        Document document = builder.buildDocument();
        URI uri = binding.generateRedirectUri(GeneralConstants.SAML_RESPONSE_KEY, APP_SERVER_BASE_URL + "/employee-sig/saml", document);
        response = client.target(uri).request().get();
        String errorPage = response.readEntity(String.class);
        response.close();
        Assert.assertTrue(errorPage.contains("Error Page"));
        client.close();
        Assert.assertNotNull(ErrorServlet.authError);
        SamlAuthenticationError error = (SamlAuthenticationError)ErrorServlet.authError;
        Assert.assertEquals(SamlAuthenticationError.Reason.ERROR_STATUS, error.getReason());
        Assert.assertNotNull(error.getStatus());
        ErrorServlet.authError = null;

    }

    public void testPostSimpleLoginLogout() {
        driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post/");
        assertAtLoginPagePostBinding();
        loginPage.login("bburke", "password");
        assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post/");
        System.out.println(driver.getPageSource());
        Assert.assertTrue(driver.getPageSource().contains("bburke"));
        driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post?GLO=true");
        checkLoggedOut(APP_SERVER_BASE_URL + "/sales-post/", true);
    }

    public void testPostPassiveLoginLogout(boolean forbiddenIfNotauthenticated) {
        // first request on passive app - no login page shown, user not logged in as we are in passive mode.
        // Shown page depends on used authentication mechanism, some may return forbidden error, some return requested page with anonymous user (not logged in)
        driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-passive/");
        assertEquals(APP_SERVER_BASE_URL + "/sales-post-passive/saml", driver.getCurrentUrl());
        System.out.println(driver.getPageSource());
        if (forbiddenIfNotauthenticated) {
            Assert.assertTrue(driver.getPageSource().contains("HTTP status code: 403"));
        } else {
            Assert.assertTrue(driver.getPageSource().contains("principal=null"));
        }

        // login user by asking login from other app
        driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post/");
        loginPage.login("bburke", "password");

        // navigate to the passive app again, we have to be logged in now
        driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-passive/");
        assertEquals(APP_SERVER_BASE_URL + "/sales-post-passive/", driver.getCurrentUrl());
        System.out.println(driver.getPageSource());
        Assert.assertTrue(driver.getPageSource().contains("bburke"));

        // logout from both app
        driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-passive?GLO=true");
        driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post?GLO=true");

        // refresh passive app page, not logged in again as we are in passive mode
        driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-passive/");
        assertEquals(APP_SERVER_BASE_URL + "/sales-post-passive/saml", driver.getCurrentUrl());
        Assert.assertFalse(driver.getPageSource().contains("bburke"));
    }

    public void testPostSimpleUnauthorized(CheckAuthError error) {
        driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post/");
        assertAtLoginPagePostBinding();
        loginPage.login("unauthorized", "password");
        assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post/");
        System.out.println(driver.getPageSource());
        error.check(driver);
    }

    public void testPostSimpleLoginLogoutIdpInitiated() {
        driver.navigate().to(AUTH_SERVER_URL + "/realms/demo/protocol/saml/clients/sales-post");
        loginPage.login("bburke", "password");
        Assert.assertTrue(driver.getCurrentUrl().startsWith(APP_SERVER_BASE_URL + "/sales-post"));
        System.out.println(driver.getPageSource());
        Assert.assertTrue(driver.getPageSource().contains("bburke"));
        driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post?GLO=true");
        checkLoggedOut(APP_SERVER_BASE_URL + "/sales-post/", true);
    }

    public void testPostSimpleLoginLogoutIdpInitiatedRedirectTo() {
        driver.navigate().to(AUTH_SERVER_URL + "/realms/demo/protocol/saml/clients/sales-post2");
        loginPage.login("bburke", "password");
        assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post2/foo");
        System.out.println(driver.getPageSource());
        Assert.assertTrue(driver.getPageSource().contains("bburke"));
        driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post2?GLO=true");
        checkLoggedOut(APP_SERVER_BASE_URL + "/sales-post2/", true);
    }

    public void testPostSignedLoginLogout() {
        driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-sig/");
        assertAtLoginPagePostBinding();
        loginPage.login("bburke", "password");
        assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post-sig/");
        Assert.assertTrue(driver.getPageSource().contains("bburke"));
        driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-sig?GLO=true");
        checkLoggedOut(APP_SERVER_BASE_URL + "/sales-post-sig/", true);

    }
    public void testPostSignedResponseAndAssertionLoginLogout() {
        driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-assertion-and-response-sig/");
        assertAtLoginPagePostBinding();
        loginPage.login("bburke", "password");
        assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post-assertion-and-response-sig/");
        Assert.assertTrue(driver.getPageSource().contains("bburke"));
        driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-assertion-and-response-sig?GLO=true");
        checkLoggedOut(APP_SERVER_BASE_URL + "/sales-post-assertion-and-response-sig/", true);

    }
    public void testPostSignedLoginLogoutTransientNameID() {
        driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-sig-transient/");
        assertAtLoginPagePostBinding();
        loginPage.login("bburke", "password");
        assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post-sig-transient/");
        System.out.println(driver.getPageSource());
        Assert.assertFalse(driver.getPageSource().contains("bburke"));
        Assert.assertTrue(driver.getPageSource().contains("principal=G-"));
        driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-sig-transient?GLO=true");
        checkLoggedOut(APP_SERVER_BASE_URL + "/sales-post-sig-transient/", true);

    }
    public void testPostSignedLoginLogoutPersistentNameID() {
        driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-sig-persistent/");
        assertAtLoginPagePostBinding();
        loginPage.login("bburke", "password");
        assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post-sig-persistent/");
        System.out.println(driver.getPageSource());
        Assert.assertFalse(driver.getPageSource().contains("bburke"));
        Assert.assertTrue(driver.getPageSource().contains("principal=G-"));
        driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-sig-persistent?GLO=true");
        checkLoggedOut(APP_SERVER_BASE_URL + "/sales-post-sig-persistent/", true);

    }
    public void testPostSignedLoginLogoutEmailNameID() {
        driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-sig-email/");
        assertAtLoginPagePostBinding();
        loginPage.login("bburke", "password");
        assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post-sig-email/");
        System.out.println(driver.getPageSource());
        Assert.assertTrue(driver.getPageSource().contains("principal=bburke@redhat.com"));
        driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-sig-email?GLO=true");
        checkLoggedOut(APP_SERVER_BASE_URL + "/sales-post-sig-email/", true);

    }

    public void testRelayStateEncoding() throws Exception {
        // this test has a hardcoded SAMLRequest and we hack a SP face servlet to get the SAMLResponse so we can look
        // at the relay state
        SamlSPFacade.samlResponse = null;
        driver.navigate().to(APP_SERVER_BASE_URL + "/employee/");
        assertAtLoginPageRedirectBinding();
        System.out.println(driver.getCurrentUrl());
        loginPage.login("bburke", "password");
        assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee/");
        assertEquals(SamlSPFacade.sentRelayState, SamlSPFacade.RELAY_STATE);
        Assert.assertNotNull(SamlSPFacade.samlResponse);

    }

    public void testAttributes() throws Exception {
        keycloakRule.update(new KeycloakRule.KeycloakSetup() {
            @Override
            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
                ClientModel app = appRealm.getClientByClientId(APP_SERVER_BASE_URL + "/employee2/");
                app.addProtocolMapper(GroupMembershipMapper.create("groups", "group", null, null, true));
                app.addProtocolMapper(UserAttributeStatementMapper.createAttributeMapper("topAttribute", "topAttribute", "topAttribute", "Basic", null, false, null));
                app.addProtocolMapper(UserAttributeStatementMapper.createAttributeMapper("level2Attribute", "level2Attribute", "level2Attribute", "Basic", null, false, null));
            }
        }, "demo");
        {
            SendUsernameServlet.sentPrincipal = null;
            SendUsernameServlet.checkRoles = null;
            driver.navigate().to(APP_SERVER_BASE_URL + "/employee2/");
            assertAtLoginPagePostBinding();
            List<String> requiredRoles = new LinkedList<>();
            requiredRoles.add("manager");
            requiredRoles.add("user");
            SendUsernameServlet.checkRoles = requiredRoles;
            loginPage.login("level2GroupUser", "password");
            assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee2/");
            SendUsernameServlet.checkRoles = null;
            SamlPrincipal principal = (SamlPrincipal) SendUsernameServlet.sentPrincipal;
            Assert.assertNotNull(principal);
            assertEquals("level2@redhat.com", principal.getAttribute(X500SAMLProfileConstants.EMAIL.get()));
            assertEquals("true", principal.getAttribute("topAttribute"));
            assertEquals("true", principal.getAttribute("level2Attribute"));
            List<String> groups = principal.getAttributes("group");
            Assert.assertNotNull(groups);
            Set<String> groupSet = new HashSet<>();
            assertEquals("level2@redhat.com", principal.getFriendlyAttribute("email"));
            driver.navigate().to(APP_SERVER_BASE_URL + "/employee2/?GLO=true");
            checkLoggedOut(APP_SERVER_BASE_URL + "/employee2/", true);

        }
        {
            SendUsernameServlet.sentPrincipal = null;
            SendUsernameServlet.checkRoles = null;
            driver.navigate().to(APP_SERVER_BASE_URL + "/employee2/");
            assertAtLoginPagePostBinding();
            List<String> requiredRoles = new LinkedList<>();
            requiredRoles.add("manager");
            requiredRoles.add("employee");
            requiredRoles.add("user");
            SendUsernameServlet.checkRoles = requiredRoles;
            loginPage.login("bburke", "password");
            assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee2/");
            SendUsernameServlet.checkRoles = null;
            SamlPrincipal principal = (SamlPrincipal) SendUsernameServlet.sentPrincipal;
            Assert.assertNotNull(principal);
            assertEquals("bburke@redhat.com", principal.getAttribute(X500SAMLProfileConstants.EMAIL.get()));
            assertEquals("bburke@redhat.com", principal.getFriendlyAttribute("email"));
            assertEquals("617", principal.getAttribute("phone"));
            Assert.assertNull(principal.getFriendlyAttribute("phone"));
            driver.navigate().to(APP_SERVER_BASE_URL + "/employee2/?GLO=true");
            checkLoggedOut(APP_SERVER_BASE_URL + "/employee2/", true);

        }
        keycloakRule.update(new KeycloakRule.KeycloakSetup() {
            @Override
            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
                ClientModel app = appRealm.getClientByClientId(APP_SERVER_BASE_URL + "/employee2/");
                for (ProtocolMapperModel mapper : app.getProtocolMappers()) {
                    if (mapper.getName().equals("role-list")) {
                        app.removeProtocolMapper(mapper);
                        mapper.setId(null);
                        mapper.getConfig().put(RoleListMapper.SINGLE_ROLE_ATTRIBUTE, "true");
                        mapper.getConfig().put(AttributeStatementHelper.SAML_ATTRIBUTE_NAME, "memberOf");
                        app.addProtocolMapper(mapper);
                    }
                }
                app.addProtocolMapper(HardcodedAttributeMapper.create("hardcoded-attribute", "hardcoded-attribute", "Basic", null, "hard", false, null));
                app.addProtocolMapper(HardcodedRole.create("hardcoded-role", "hardcoded-role"));
                app.addProtocolMapper(RoleNameMapper.create("renamed-role", "manager", "el-jefe"));
                app.addProtocolMapper(RoleNameMapper.create("renamed-employee-role", APP_SERVER_BASE_URL + "/employee/.employee", "pee-on"));
            }
        }, "demo");

        System.out.println(">>>>>>>>>> single role attribute <<<<<<<<");

        {
            SendUsernameServlet.sentPrincipal = null;
            SendUsernameServlet.checkRoles = null;
            driver.navigate().to(APP_SERVER_BASE_URL + "/employee2/");
            assertAtLoginPagePostBinding();
            List<String> requiredRoles = new LinkedList<>();
            requiredRoles.add("el-jefe");
            requiredRoles.add("user");
            requiredRoles.add("hardcoded-role");
            requiredRoles.add("pee-on");
            SendUsernameServlet.checkRoles = requiredRoles;
            loginPage.login("bburke", "password");
            assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee2/");
            SendUsernameServlet.checkRoles = null;
            SamlPrincipal principal = (SamlPrincipal) SendUsernameServlet.sentPrincipal;
            Assert.assertNotNull(principal);
            assertEquals("hard", principal.getAttribute("hardcoded-attribute"));


        }
    }

    public void testRedirectSignedLoginLogout() {
        driver.navigate().to(APP_SERVER_BASE_URL + "/employee-sig/");
        assertAtLoginPageRedirectBinding();
        loginPage.login("bburke", "password");
        assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee-sig/");
        Assert.assertTrue(driver.getPageSource().contains("bburke"));
        driver.navigate().to(APP_SERVER_BASE_URL + "/employee-sig?GLO=true");
        checkLoggedOut(APP_SERVER_BASE_URL + "/employee-sig/", false);

    }

    public void testRedirectSignedLoginLogoutFrontNoSSO() {
        driver.navigate().to(APP_SERVER_BASE_URL + "/employee-sig-front/");
        assertAtLoginPageRedirectBinding();
        loginPage.login("bburke", "password");
        assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee-sig-front/");
        Assert.assertTrue(driver.getPageSource().contains("bburke"));
        driver.navigate().to(APP_SERVER_BASE_URL + "/employee-sig-front?GLO=true");
        checkLoggedOut(APP_SERVER_BASE_URL + "/employee-sig-front/", false);

    }

    public void testRedirectSignedLoginLogoutFront() {
        // visit 1st app an logg in
        System.out.println("visit 1st app ");
        driver.navigate().to(APP_SERVER_BASE_URL + "/employee-sig/");
        assertAtLoginPageRedirectBinding();
        System.out.println("login to form");
        loginPage.login("bburke", "password");
        assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee-sig/");
        Assert.assertTrue(driver.getPageSource().contains("bburke"));

        // visit 2nd app
        System.out.println("visit 2nd app ");
        driver.navigate().to(APP_SERVER_BASE_URL + "/employee-sig-front/");
        assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee-sig-front/");
        Assert.assertTrue(driver.getPageSource().contains("bburke"));

        // visit 3rd app
        System.out.println("visit 3rd app ");
        driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-sig/");
        assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-post-sig/");
        Assert.assertTrue(driver.getPageSource().contains("bburke"));

        // logout of first app
        System.out.println("GLO");
        driver.navigate().to(APP_SERVER_BASE_URL + "/employee-sig?GLO=true");
        checkLoggedOut(APP_SERVER_BASE_URL + "/employee-sig/", false);
        driver.navigate().to(APP_SERVER_BASE_URL + "/employee-sig-front/");
        String currentUrl = driver.getCurrentUrl();
        Assert.assertTrue(currentUrl.startsWith(AUTH_SERVER_URL + "/realms/demo/protocol/saml"));
        driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-sig/");
        assertAtLoginPagePostBinding();

    }

    public void testPostEncryptedLoginLogout() {
        driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-enc/");
        assertAtLoginPagePostBinding();
        loginPage.login("bburke", "password");
        Retry.execute(new Runnable() {
            @Override
            public void run() {
                assertEquals(APP_SERVER_BASE_URL + "/sales-post-enc/", driver.getCurrentUrl());
            }
        }, 10, 100);
        Assert.assertTrue(driver.getPageSource().contains("bburke"));
        driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-enc?GLO=true");
        checkLoggedOut(APP_SERVER_BASE_URL + "/sales-post-enc/", true);

    }
    public void testPostBadClientSignature() {
        driver.navigate().to(APP_SERVER_BASE_URL + "/bad-client-sales-post-sig/");
        System.out.println(driver.getCurrentUrl());
        Assert.assertTrue(driver.getCurrentUrl().startsWith(AUTH_SERVER_URL + "/realms/demo/protocol/saml"));
        assertEquals(PageUtils.getPageTitle(driver), "We're sorry...");

    }
    public static interface CheckAuthError {
        void check(WebDriver driver);
    }

    public void testPostBadRealmSignature() {
        ErrorServlet.authError = null;
        driver.navigate().to(APP_SERVER_BASE_URL + "/bad-realm-sales-post-sig/");
        assertAtLoginPagePostBinding();
        loginPage.login("bburke", "password");
        assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/bad-realm-sales-post-sig/saml");
        System.out.println(driver.getPageSource());
        Assert.assertNotNull(ErrorServlet.authError);
        SamlAuthenticationError error = (SamlAuthenticationError)ErrorServlet.authError;
        Assert.assertEquals(SamlAuthenticationError.Reason.INVALID_SIGNATURE, error.getReason());
        ErrorServlet.authError = null;
    }

    public void testPostBadAssertionSignature() {
        ErrorServlet.authError = null;
        driver.navigate().to(APP_SERVER_BASE_URL + "/bad-assertion-sales-post-sig/");
        assertAtLoginPagePostBinding();
        loginPage.login("bburke", "password");
        assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/bad-assertion-sales-post-sig/saml");
        System.out.println(driver.getPageSource());
        Assert.assertNotNull(ErrorServlet.authError);
        SamlAuthenticationError error = (SamlAuthenticationError)ErrorServlet.authError;
        Assert.assertEquals(SamlAuthenticationError.Reason.INVALID_SIGNATURE, error.getReason());
        ErrorServlet.authError = null;
    }

    public void testMissingAssertionSignature() {
        ErrorServlet.authError = null;
        driver.navigate().to(APP_SERVER_BASE_URL + "/missing-assertion-sig/");
        assertAtLoginPagePostBinding();
        loginPage.login("bburke", "password");
        assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/missing-assertion-sig/saml");
        System.out.println(driver.getPageSource());
        Assert.assertNotNull(ErrorServlet.authError);
        SamlAuthenticationError error = (SamlAuthenticationError)ErrorServlet.authError;
        Assert.assertEquals(SamlAuthenticationError.Reason.INVALID_SIGNATURE, error.getReason());
        ErrorServlet.authError = null;
    }

    public void testMetadataPostSignedLoginLogout() throws Exception {

        driver.navigate().to(APP_SERVER_BASE_URL + "/sales-metadata/");
        assertAtLoginPagePostBinding();
        loginPage.login("bburke", "password");
        assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/sales-metadata/");
        String pageSource = driver.getPageSource();
        Assert.assertTrue(pageSource.contains("bburke"));
        driver.navigate().to(APP_SERVER_BASE_URL + "/sales-metadata?GLO=true");
        checkLoggedOut(APP_SERVER_BASE_URL + "/sales-metadata/", true);

    }

    public static void uploadSP(String AUTH_SERVER_URL) {
        try {
            Keycloak keycloak = Keycloak.getInstance(AUTH_SERVER_URL, "master", "admin", "admin", Constants.ADMIN_CLI_CLIENT_ID, null);
            RealmResource admin = keycloak.realm("demo");

            admin.toRepresentation();

            ClientRepresentation clientRep = admin.convertClientDescription(IOUtils.toString(SamlAdapterTestStrategy.class.getResourceAsStream("/keycloak-saml/sp-metadata.xml")));
            Response response = admin.clients().create(clientRep);

            assertEquals(201, response.getStatus());

            keycloak.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
