package org.keycloak.testsuite.x509;

import io.undertow.Undertow;

import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.function.Supplier;

import jakarta.ws.rs.core.Response;

import org.apache.commons.io.IOUtils;
import org.apache.http.impl.client.CloseableHttpClient;
import org.jboss.arquillian.drone.api.annotation.Drone;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.testsuite.util.HtmlUnitBrowser;
import org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel;
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
import org.keycloak.testsuite.util.OAuthClient;
import org.openqa.selenium.WebDriver;

import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.IdentityMapperType.USERNAME_EMAIL;
import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.SUBJECTDN_EMAIL;

public class X509OCSPResponderFailOpenTest extends AbstractX509AuthenticationTest {

    private static final String OCSP_RESPONDER_HOST = "localhost";

    private static final int OCSP_RESPONDER_PORT = 8888;

    private Undertow ocspResponder;

    @Drone
    @HtmlUnitBrowser
    private WebDriver htmlUnit;

    @Before
    public void replaceTheDefaultDriver() {
        replaceDefaultWebDriver(htmlUnit);
    }

    @Test
    public void ocspFailCloseLoginFailed() throws Exception {
        // Test of OCSP failure (invalid OCSP responder host) when OCSP Fail-Open is set to OFF
        // If test is successful, it should return an auth error

        X509AuthenticatorConfigModel config = new X509AuthenticatorConfigModel()
                .setOCSPEnabled(true)
                .setOCSPResponder("http://" + OCSP_RESPONDER_HOST + ".invalid.host:" + OCSP_RESPONDER_PORT + "/oscp")
                .setOCSPFailOpen(false)
                .setMappingSourceType(SUBJECTDN_EMAIL)
                .setUserIdentityMapperType(USERNAME_EMAIL);
        AuthenticatorConfigRepresentation cfg = newConfig("x509-directgrant-config", config.getConfig());
        String cfgId = createConfig(directGrantExecution.getId(), cfg);
        Assert.assertNotNull(cfgId);

        oauth.clientId("resource-owner");
        OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "", "", null);

        assertEquals(Response.Status.UNAUTHORIZED.getStatusCode(), response.getStatusCode());
        assertEquals("invalid_request", response.getError());

        // Make sure we got the right error
        assertThat(response.getErrorDescription(), containsString("OCSP check failed"));
    }

    @Test
    public void ocspFailOpenLoginSuccess() throws Exception {
        // Test of OCSP failure (invalid OCSP responder host) when OCSP Fail-Open is set to ON
        // If test is successful, it should continue the login

        X509AuthenticatorConfigModel config =
                new X509AuthenticatorConfigModel()
                        .setOCSPEnabled(true)
                        .setOCSPFailOpen(true)
                        .setMappingSourceType(SUBJECTDN_EMAIL)
                        .setOCSPResponder("http://" + OCSP_RESPONDER_HOST + ".invalid.host:" + OCSP_RESPONDER_PORT + "/oscp")
                        .setOCSPResponderCertificate(
                                IOUtils.toString(this.getClass().getResourceAsStream(OcspHandler.OCSP_RESPONDER_CERT_PATH), StandardCharsets.UTF_8)
                                        .replace("-----BEGIN CERTIFICATE-----", "")
                                        .replace("-----END CERTIFICATE-----", ""))
                        .setUserIdentityMapperType(USERNAME_EMAIL);
        AuthenticatorConfigRepresentation cfg = newConfig("x509-directgrant-config", config.getConfig());
        String cfgId = createConfig(directGrantExecution.getId(), cfg);
        Assert.assertNotNull(cfgId);

        String keyStorePath = Paths.get(System.getProperty("client.certificate.keystore"))
                .getParent().resolve("client-ca.jks").toString();
        String keyStorePassword = System.getProperty("client.certificate.keystore.passphrase");
        String trustStorePath = System.getProperty("client.truststore");
        String trustStorePassword = System.getProperty("client.truststore.passphrase");
        Supplier<CloseableHttpClient> previous = oauth.getHttpClient();
        try {
            oauth.clientId("resource-owner");
            oauth.httpClient(() -> OAuthClient.newCloseableHttpClientSSL(keyStorePath, keyStorePassword, trustStorePath, trustStorePassword));
            OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "", "", null);

            // Make sure authentication is allowed
            assertEquals(Response.Status.OK.getStatusCode(), response.getStatusCode());
        } finally {
            oauth.httpClient(previous);
        }
    }

}