package net.unicon.cas.mfa.web.support;

import net.unicon.cas.mfa.authentication.AuthenticationMethod;
import net.unicon.cas.mfa.authentication.JsonBackedAuthenticationMethodConfigurationProvider;
import org.jasig.cas.authentication.principal.Service;
import org.jasig.cas.support.saml.web.support.SamlArgumentExtractor;
import org.jasig.cas.web.support.ArgumentExtractor;
import org.jasig.cas.web.support.CasArgumentExtractor;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;

import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

@RunWith(JUnit4.class)
public class RequestParameterMultiFactorAuthenticationArgumentExtractorTests {

    private final List<ArgumentExtractor> supportedArgumentExtractors;

    private final MultiFactorWebApplicationServiceFactory mfaWebApplicationServiceFactory;

    public RequestParameterMultiFactorAuthenticationArgumentExtractorTests() {
        this.supportedArgumentExtractors = new ArrayList<>();
        this.supportedArgumentExtractors.add(new CasArgumentExtractor());
        this.supportedArgumentExtractors.add(new SamlArgumentExtractor());
        this.mfaWebApplicationServiceFactory = new DefaultMultiFactorWebApplicationServiceFactory();
    }

    /**
     * When login presents no authentication method, the extractor extracts a null service.
     */
    @Test
    public void testMissingAuthenticationMethodParameterYieldsNullService() {

        // let's say we support all sorts of interesting authentication methods,
        // but this login request isn't going to require any of these
        final SortedSet<AuthenticationMethod> validAuthenticationMethods =
                new TreeSet<>();
        validAuthenticationMethods.add(new AuthenticationMethod("fingerprint", 1));
        validAuthenticationMethods.add(new AuthenticationMethod("retina_scan", 2));
        validAuthenticationMethods.add(new AuthenticationMethod("personal_attestation", 3));
        validAuthenticationMethods.add(new AuthenticationMethod("strong_two_factor", 4));

        final JsonBackedAuthenticationMethodConfigurationProvider loader =
                new JsonBackedAuthenticationMethodConfigurationProvider(validAuthenticationMethods);

        final RequestParameterMultiFactorAuthenticationArgumentExtractor extractor =
                new RequestParameterMultiFactorAuthenticationArgumentExtractor(this.supportedArgumentExtractors,
                        this.mfaWebApplicationServiceFactory, new DefaultAuthenticationMethodVerifier(loader));

        final HttpServletRequest request = mock(HttpServletRequest.class);

        when(request.getParameter("service")).thenReturn("https://www.github.com");

        // let's say the authn_method request parameter is missing outright
        when(request.getParameter(MultiFactorAuthenticationSupportingWebApplicationService.CONST_PARAM_AUTHN_METHOD))
                .thenReturn(null);

        assertNull(extractor.extractService(request));
    }


    /**
     * When login presents an unrecognized authentication method, the extractor extracts a null service.
     */
    @Test(expected = UnrecognizedAuthenticationMethodException.class)
    public void testUnrecognizedAuthenticationMethodParameterYieldsNullService() {
        final JsonBackedAuthenticationMethodConfigurationProvider loader = new JsonBackedAuthenticationMethodConfigurationProvider();

        final RequestParameterMultiFactorAuthenticationArgumentExtractor extractor =
                new RequestParameterMultiFactorAuthenticationArgumentExtractor(this.supportedArgumentExtractors,
                        this.mfaWebApplicationServiceFactory, new DefaultAuthenticationMethodVerifier(loader));


        final HttpServletRequest request = mock(HttpServletRequest.class);

        when(request.getParameter("service")).thenReturn("https://www.github.com");
        when(request.getParameter(MultiFactorAuthenticationSupportingWebApplicationService.CONST_PARAM_AUTHN_METHOD))
                .thenReturn("unrecognized_authentication_method");

        extractor.extractService(request);
    }

    /**
     * When login presents the one recognized authentication method, extractor extracts a service conveying the
     * required authentication method.
     */
    @Test
    public void testRecognizedAuthenticationMethodParameterYieldsAuthenticationMethodRequiringService() {
        final SortedSet<AuthenticationMethod> validAuthenticationMethods =
                new TreeSet<>();
        validAuthenticationMethods.add(new AuthenticationMethod("strong_two_factor", 1));
        final JsonBackedAuthenticationMethodConfigurationProvider loader =
                new JsonBackedAuthenticationMethodConfigurationProvider(validAuthenticationMethods);

        final RequestParameterMultiFactorAuthenticationArgumentExtractor extractor =
                new RequestParameterMultiFactorAuthenticationArgumentExtractor(this.supportedArgumentExtractors,
                        this.mfaWebApplicationServiceFactory, new DefaultAuthenticationMethodVerifier(loader));

        final HttpServletRequest request = mock(HttpServletRequest.class);

        when(request.getParameter("service")).thenReturn("https://www.github.com");
        when(request.getParameter(MultiFactorAuthenticationSupportingWebApplicationService.CONST_PARAM_AUTHN_METHOD))
                .thenReturn("strong_two_factor");

        assertTrue(extractor.extractService(request) instanceof MultiFactorAuthenticationSupportingWebApplicationService);

         final MultiFactorAuthenticationSupportingWebApplicationService authenticationMethodRequiringService =
                (MultiFactorAuthenticationSupportingWebApplicationService) extractor.extractService(request);

        assertNotNull(authenticationMethodRequiringService);
        assertEquals("strong_two_factor", authenticationMethodRequiringService.getAuthenticationMethod());
    }

    /**
     * When login presents a recognized authentication method among several supported methods,
     * extractor extracts a service conveying the required authentication method.
     * @throws IOException the IO exception
     */
    @Test
    public void testRecognizedAuthenticationMethodParamAmongMultipleSupportedYieldsService() throws IOException {

        // this is a bit of testing paranoia, but always want to check that one item isn't an edge case
        final SortedSet<AuthenticationMethod> validAuthenticationMethods =
                new TreeSet<>();
        validAuthenticationMethods.add(new AuthenticationMethod("fingerprint", 1));
        validAuthenticationMethods.add(new AuthenticationMethod("retina_scan", 2));
        validAuthenticationMethods.add(new AuthenticationMethod("personal_attestation", 3));
        validAuthenticationMethods.add(new AuthenticationMethod("strong_two_factor", 4));

        final JsonBackedAuthenticationMethodConfigurationProvider loader =
                new JsonBackedAuthenticationMethodConfigurationProvider(validAuthenticationMethods);

        final RequestParameterMultiFactorAuthenticationArgumentExtractor extractor =
                new RequestParameterMultiFactorAuthenticationArgumentExtractor(this.supportedArgumentExtractors,
                        this.mfaWebApplicationServiceFactory, new DefaultAuthenticationMethodVerifier(
                        loader));


        final HttpServletRequest request = mock(HttpServletRequest.class);

        when(request.getParameter("service")).thenReturn("https://www.github.com");
        when(request.getParameter(MultiFactorAuthenticationSupportingWebApplicationService.CONST_PARAM_AUTHN_METHOD))
                .thenReturn("personal_attestation");

        assertTrue(extractor.extractService(request) instanceof MultiFactorAuthenticationSupportingWebApplicationService);

        final MultiFactorAuthenticationSupportingWebApplicationService authenticationMethodRequiringService =
                (MultiFactorAuthenticationSupportingWebApplicationService) extractor.extractService(request);

        assertNotNull(authenticationMethodRequiringService);
        assertEquals("personal_attestation", authenticationMethodRequiringService.getAuthenticationMethod());
    }

    /**
     * When login presents no service parameter, extractor extracts a null service.
     */
    @Test
    public void testMissingServiceParameterYieldsNullService() {

        // this is a bit of testing paranoia, but always want to check that one item isn't an edge case
        final SortedSet<AuthenticationMethod> validAuthenticationMethods =
                new TreeSet<>();
        validAuthenticationMethods.add(new AuthenticationMethod("fingerprint", 1));
        validAuthenticationMethods.add(new AuthenticationMethod("retina_scan", 2));
        validAuthenticationMethods.add(new AuthenticationMethod("personal_attestation", 3));
        validAuthenticationMethods.add(new AuthenticationMethod("strong_two_factor", 4));

        final JsonBackedAuthenticationMethodConfigurationProvider loader =
                new JsonBackedAuthenticationMethodConfigurationProvider(validAuthenticationMethods);
        final RequestParameterMultiFactorAuthenticationArgumentExtractor extractor =
                new RequestParameterMultiFactorAuthenticationArgumentExtractor(this.supportedArgumentExtractors,
                        this.mfaWebApplicationServiceFactory, new DefaultAuthenticationMethodVerifier(loader));


        final HttpServletRequest request = mock(HttpServletRequest.class);

        when(request.getParameter("service")).thenReturn(null);
        when(request.getParameter(MultiFactorAuthenticationSupportingWebApplicationService.CONST_PARAM_AUTHN_METHOD))
                .thenReturn("personal_attestation");

        assertNull(extractor.extractService(request));

    }

    @Test
    public void testRecognizedAuthenticationMethodParameterInSamlRequest() {
        final SortedSet<AuthenticationMethod> validAuthenticationMethods =
                new TreeSet<>();
        validAuthenticationMethods.add(new AuthenticationMethod("strong_two_factor", 1));
        final JsonBackedAuthenticationMethodConfigurationProvider loader =
                new JsonBackedAuthenticationMethodConfigurationProvider(validAuthenticationMethods);

        final RequestParameterMultiFactorAuthenticationArgumentExtractor extractor =
                new RequestParameterMultiFactorAuthenticationArgumentExtractor(this.supportedArgumentExtractors,
                        this.mfaWebApplicationServiceFactory, new DefaultAuthenticationMethodVerifier(loader));


        final HttpServletRequest request = mock(HttpServletRequest.class);

        when(request.getParameter("TARGET")).thenReturn("https://www.github.com");
        when(request.getParameter(MultiFactorAuthenticationSupportingWebApplicationService.CONST_PARAM_AUTHN_METHOD))
                .thenReturn("strong_two_factor");

        final Service svc = extractor.extractService(request);
        assertTrue(svc instanceof MultiFactorAuthenticationSupportingWebApplicationService);

        final MultiFactorAuthenticationSupportingWebApplicationService authenticationMethodRequiringService =
                (MultiFactorAuthenticationSupportingWebApplicationService) extractor.extractService(request);

        assertNotNull(authenticationMethodRequiringService);
        assertEquals("strong_two_factor", authenticationMethodRequiringService.getAuthenticationMethod());
    }
}