package com.thinkbiganalytics.auth.jwt;

/*-
 * #%L
 * thinkbig-security-auth
 * %%
 * Copyright (C) 2017 ThinkBig Analytics
 * %%
 * 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.
 * #L%
 */

import com.thinkbiganalytics.auth.config.JwtProperties;

import org.joda.time.DateTimeUtils;
import org.jose4j.jws.AlgorithmIdentifiers;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.rememberme.InvalidCookieException;

import java.util.Collections;
import java.util.concurrent.atomic.AtomicReference;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class JwtRememberMeServicesTest {

    /**
     * The JWT Remember Me service to be tested
     */
    private JwtRememberMeServices service;

    /**
     * Create service and setup environment for tests
     */
    @Before
    public void setUp() {
        DateTimeUtils.setCurrentMillisFixed(1461942300000L);

        final JwtProperties properties = new JwtProperties();
        properties.setAlgorithm(AlgorithmIdentifiers.HMAC_SHA256);
        properties.setKey("https://www.thinkbiganalytics.com/");
        service = new JwtRememberMeServices(properties);
    }

    /**
     * Reset date/time after tests.
     */
    @After
    public void tearDown() {
        DateTimeUtils.setCurrentMillisSystem();
    }

    /**
     * Verifies token deserialization.
     */
    @Test
    public void decodeCookie() {
        // Test with no groups
        String[] actual = service.decodeCookie("eyJhbGciOiJIUzI1NiIsImtpZCI6IkhNQUMifQ.eyJleHAiOjE0NjMxNTE5MDAsInN1YiI6InVzZXIiLCJncm91cHMiOltdfQ.NKAD4m4bn1eDMGA9HSihOsLwrSUcDhBYfXIL7uJPWdo");
        String[] expected = new String[]{"user"};
        Assert.assertArrayEquals(expected, actual);

        // Test with one group
        actual = service.decodeCookie("eyJhbGciOiJIUzI1NiIsImtpZCI6IkhNQUMifQ.eyJleHAiOjE0NjMxNTE5MDAsInN1YiI6ImRsYWRtaW4iLCJncm91cHMiOlsiYWRtaW4iXX0."
                                      + "3RX9hsmfNA1rtWMs309N5MV5_gd4FUtU_odFIIOqsoY");
        expected = new String[]{"dladmin", "admin"};
        Assert.assertArrayEquals(expected, actual);

        // Test with multiple groups
        actual = service.decodeCookie("eyJhbGciOiJIUzI1NiIsImtpZCI6IkhNQUMifQ.eyJleHAiOjE0NjMxNTE5MDAsInN1YiI6ImRsYWRtaW4iLCJncm91cHMiOlsiZGVzaWduZXJzIiwib3BlcmF0b3JzIl19."
                                      + "fRxn00QbHAjL-R0DI1DmYfLEi3F7eMb3V2vTvgcFOy8");
        expected = new String[]{"dladmin", "designers", "operators"};
        Assert.assertArrayEquals(expected, actual);
    }

    /**
     * Verify exception if subject is blank.
     */
    @Test(expected = InvalidCookieException.class)
    public void decodeCookieWithBlankSubject() {
        service.decodeCookie("eyJhbGciOiJIUzI1NiIsImtpZCI6IkhNQUMifQ.eyJleHAiOjE0NjMxNTE5MDAsInN1YiI6IiIsImdyb3VwcyI6W119.TZlPnjJgAW5oP9DztgE9r10rZhMv0GAnhlbGhRiMtmA");
    }

    /**
     * Verify exception if token is expired.
     */
    @Test(expected = InvalidCookieException.class)
    public void decodeCookieWithExpired() {
        service.setTokenValiditySeconds(JwtRememberMeServices.TWO_WEEKS_S);
        service.decodeCookie("eyJhbGciOiJIUzI1NiIsImtpZCI6IkhNQUMifQ.eyJleHAiOjE0NjE5NDIzMDAsInN1YiI6ImRsYWRtaW4iLCJncm91cHMiOlsiYWRtaW4iXX0.AsXMAoAM1EPAw5mk4YHNXWsB9H8-lVf4JrQ6K9zHIfw");
    }

    /**
     * Verify exception if token signature is invalid.
     */
    @Test(expected = InvalidCookieException.class)
    public void decodeCookieWithInvalid() {
        service.decodeCookie("eyJhbGciOiJIUzI1NiIsImtpZCI6IkhNQUMifQ.eyJleHAiOjE0NjMxNTE5MDAsInN1YiI6IiIsImdyb3VwcyI6W119.L_00dw3cpWbxw32Pddj6Jq1xeFqPf8ZFdPWAUdmj39k");
    }

    /**
     * Verify exception if subject is missing.
     */
    @Test(expected = InvalidCookieException.class)
    public void decodeCookieWithMissingSubject() {
        service.decodeCookie("eyJhbGciOiJIUzI1NiIsImtpZCI6IkhNQUMifQ.eyJleHAiOjE0NjMxNTE5MDB9.L_00dw3cpWbxw32Pddj6Jq1xeFqPf8ZFdPWAUdmj39k");
    }

    /**
     * Verifies token serialization.
     */
    @Test
    public void encodeCookie() {
        // Test with no groups
        String actual = service.encodeCookie(new String[]{"user"});
        String expected = "eyJhbGciOiJIUzI1NiIsImtpZCI6IkhNQUMifQ.eyJleHAiOjE0NjMxNTE5MDAsInN1YiI6InVzZXIiLCJncm91cHMiOltdfQ.NKAD4m4bn1eDMGA9HSihOsLwrSUcDhBYfXIL7uJPWdo";
        Assert.assertEquals(expected, actual);

        // Test with one group
        actual = service.encodeCookie(new String[]{"dladmin", "admin"});
        expected = "eyJhbGciOiJIUzI1NiIsImtpZCI6IkhNQUMifQ.eyJleHAiOjE0NjMxNTE5MDAsInN1YiI6ImRsYWRtaW4iLCJncm91cHMiOlsiYWRtaW4iXX0.3RX9hsmfNA1rtWMs309N5MV5_gd4FUtU_odFIIOqsoY";
        Assert.assertEquals(expected, actual);

        // Test with multiple groups
        actual = service.encodeCookie(new String[]{"dladmin", "designers", "operators"});
        expected = "eyJhbGciOiJIUzI1NiIsImtpZCI6IkhNQUMifQ.eyJleHAiOjE0NjMxNTE5MDAsInN1YiI6ImRsYWRtaW4iLCJncm91cHMiOlsiZGVzaWduZXJzIiwib3BlcmF0b3JzIl19.fRxn00QbHAjL-R0DI1DmYfLEi3F7eMb3V2vTvgcFOy8";
        Assert.assertEquals(expected, actual);
    }

    /**
     * Verify extracting tokens from authentication.
     */
    @Test
    public void onLoginSuccess() throws Exception {
        // Mock request, response, and authentication
        final AtomicReference<Cookie> cookie = new AtomicReference<>();
        final HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
        Mockito.doAnswer(answer -> cookie.compareAndSet(null, answer.getArgumentAt(0, Cookie.class)))
            .when(response).addCookie(Mockito.any());

        final HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
        Mockito.when(request.getContextPath()).thenReturn("");

        final Authentication auth = new UsernamePasswordAuthenticationToken("dladmin", "thinkbig", Collections.singletonList(new SimpleGrantedAuthority("admin")));

        // Test cookie set by login
        service.onLoginSuccess(request, response, auth);
        Assert.assertEquals("eyJhbGciOiJIUzI1NiIsImtpZCI6IkhNQUMifQ.eyJleHAiOjE0NjMxNTE5MDAsInN1YiI6ImRsYWRtaW4iLCJncm91cHMiOlsiYWRtaW4iXX0.3RX9hsmfNA1rtWMs309N5MV5_gd4FUtU_odFIIOqsoY",
                            cookie.get().getValue());
    }

    /**
     * Verify building a user from tokens.
     */
    @Test
    public void processAutoLoginCookie() throws Exception {
        final UserDetails user = service.processAutoLoginCookie(new String[]{"dladmin", "admin"}, Mockito.mock(HttpServletRequest.class), Mockito.mock(HttpServletResponse.class));
        Assert.assertEquals("dladmin", user.getUsername());
        Assert.assertEquals(Collections.singletonList("admin").toString(), user.getAuthorities().toString());
    }
}
