/*
 * Copyright (c) 2014-2017 Enrico M. Crisostomo
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright notice, this
 *     list of conditions and the following disclaimer.
 *
 *   * Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *
 *   * Neither the name of the author nor the names of its
 *     contributors may be used to endorse or promote products derived from
 *     this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.warrenstrange.googleauth;

import com.warrenstrange.googleauth.GoogleAuthenticatorConfig.GoogleAuthenticatorConfigBuilder;
import org.junit.BeforeClass;
import org.junit.Test;

import java.math.BigInteger;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

/**
 * Not really a unit test, but it shows the basic usage of this package.
 * To properly test the authenticator, manual intervention and multiple steps
 * are required:
 * <ol>
 * <li>Run the test in order to generate the required information for a
 * Google Authenticator application to be configured.</li>
 * <li>Set the <code>SECRET_KEY</code> field with the value generated by the
 * <code>GoogleAuthTest#createCredentials</code> method.</li>
 * <li>Generate the current code with the Google Authenticator application and
 * set the <code>VALIDATION_CODE</code> accordingly.</li>
 * <li>Check that the <code>#authorise</code> method correctly validates the
 * data when invoking the <code>GoogleAuthenticator#authorize</code> method.
 * </li>
 * </ol>
 */
public class GoogleAuthTest
{

    // Change this to the saved secret from the running the above test.
    @SuppressWarnings("SpellCheckingInspection")
    private static final String SECRET_KEY = "KR52HV2U5Z4DWGLJ";
    private static final int VALIDATION_CODE = 598775;

    @BeforeClass
    public static void setupMockCredentialRepository()
    {
        System.setProperty(
                CredentialRepositoryMock.MOCK_SECRET_KEY_NAME,
                SECRET_KEY);
    }

    private static byte[] hexStr2Bytes(String hex)
    {
        // Adding one byte to get the right conversion
        // Values starting with "0" can be converted
        byte[] bArray = new BigInteger("10" + hex, 16).toByteArray();

        // Copy all the REAL bytes, not the "first"
        byte[] ret = new byte[bArray.length - 1];
        System.arraycopy(bArray, 1, ret, 0, ret.length);

        return ret;
    }

    @Test
    public void rfc6238TestVectors()
    {
        // See RFC 6238, p. 14
        final String rfc6238TestKey = "3132333435363738393031323334353637383930";
        final byte[] key = hexStr2Bytes(rfc6238TestKey);
        final long testTime[] = {59L, 1111111109L, 1111111111L, 1234567890L, 2000000000L, 20000000000L};
        final long testResults[] = {94287082, 7081804, 14050471, 89005924, 69279037, 65353130};
        final long timeStepSizeInSeconds = 30;

        GoogleAuthenticatorConfigBuilder cb = new GoogleAuthenticatorConfigBuilder();
        cb.setCodeDigits(8).setTimeStepSizeInMillis(TimeUnit.SECONDS.toMillis(timeStepSizeInSeconds));
        GoogleAuthenticator ga = new GoogleAuthenticator(cb.build());

        for (int i = 0; i < testTime.length; ++i)
        {
            assertEquals(ga.calculateCode(key, testTime[i] / timeStepSizeInSeconds), testResults[i]);
        }
    }

    @Test
    public void rfc6238TestVectorsSHA256()
    {
        // See RFC 6238, p. 14
        final String rfc6238TestKey = "3132333435363738393031323334353637383930" +
                "313233343536373839303132";
        final byte[] key = hexStr2Bytes(rfc6238TestKey);
        final long testTime[] = {59L, 1111111109L, 1111111111L, 1234567890L, 2000000000L, 20000000000L};
        final long testResults[] = {46119246, 68084774, 67062674, 91819424, 90698825, 77737706};
        final long timeStepSizeInSeconds = 30;

        GoogleAuthenticatorConfigBuilder cb = new GoogleAuthenticatorConfigBuilder();
        cb.setCodeDigits(8).setTimeStepSizeInMillis(TimeUnit.SECONDS.toMillis(timeStepSizeInSeconds));
        cb.setHmacHashFunction(HmacHashFunction.HmacSHA256);
        GoogleAuthenticator ga = new GoogleAuthenticator(cb.build());

        for (int i = 0; i < testTime.length; ++i)
        {
            assertEquals(ga.calculateCode(key, testTime[i] / timeStepSizeInSeconds), testResults[i]);
        }
    }

    @Test
    public void rfc6238TestVectorsSHA512()
    {
        // See RFC 6238, p. 14
        final String rfc6238TestKey = "3132333435363738393031323334353637383930" +
                "3132333435363738393031323334353637383930" +
                "3132333435363738393031323334353637383930" +
                "31323334";
        final byte[] key = hexStr2Bytes(rfc6238TestKey);
        final long testTime[] = {59L, 1111111109L, 1111111111L, 1234567890L, 2000000000L, 20000000000L};
        final long testResults[] = {90693936, 25091201, 99943326, 93441116, 38618901, 47863826};
        final long timeStepSizeInSeconds = 30;

        GoogleAuthenticatorConfigBuilder cb = new GoogleAuthenticatorConfigBuilder();
        cb.setCodeDigits(8).setTimeStepSizeInMillis(TimeUnit.SECONDS.toMillis(timeStepSizeInSeconds));
        cb.setHmacHashFunction(HmacHashFunction.HmacSHA512);
        GoogleAuthenticator ga = new GoogleAuthenticator(cb.build());

        for (int i = 0; i < testTime.length; ++i)
        {
            assertEquals(ga.calculateCode(key, testTime[i] / timeStepSizeInSeconds), testResults[i]);
        }
    }

    @Test
    public void createCredentials()
    {
        GoogleAuthenticatorConfigBuilder gacb =
                new GoogleAuthenticatorConfigBuilder()
                        .setKeyRepresentation(KeyRepresentation.BASE64)
                        .setNumberOfScratchCodes(10);
        GoogleAuthenticator googleAuthenticator = new GoogleAuthenticator(gacb.build());

        final GoogleAuthenticatorKey key =
                googleAuthenticator.createCredentials();
        final String secret = key.getKey();
        final List<Integer> scratchCodes = key.getScratchCodes();

        String otpAuthURL = GoogleAuthenticatorQRGenerator.getOtpAuthURL("Test Org.", "test@prova.org", key);

        System.out.println("Please register (otpauth uri): " + otpAuthURL);
        System.out.println("Base64-encoded secret key is " + secret);

        for (Integer i : scratchCodes)
        {
            if (!googleAuthenticator.validateScratchCode(i))
            {
                throw new IllegalArgumentException("An invalid code has been " +
                        "generated: this is an application bug.");
            }
            System.out.println("Scratch code: " + i);
        }
    }

    @Test
    public void createAndAuthenticate()
    {
        final GoogleAuthenticator ga = new GoogleAuthenticator();
        final GoogleAuthenticatorKey key = ga.createCredentials();

        assertTrue(ga.authorize(key.getKey(), ga.getTotpPassword(key.getKey())));
    }

    @Test
    public void createAndAuthenticateNullAlgorithm() {
        final GoogleAuthenticator ga = new GoogleAuthenticator(null, null);
        final GoogleAuthenticatorKey key = ga.createCredentials();

        assertTrue(ga.authorize(key.getKey(), ga.getTotpPassword(key.getKey())));
    }

    @Test
    public void createCredentialsForUser()
    {
        GoogleAuthenticator googleAuthenticator = new GoogleAuthenticator();

        final GoogleAuthenticatorKey key =
                googleAuthenticator.createCredentials("testName");
        final String secret = key.getKey();
        final List<Integer> scratchCodes = key.getScratchCodes();

        String otpAuthURL = GoogleAuthenticatorQRGenerator.getOtpAuthURL("Test Org.", "test@prova.org", key);

        System.out.println("Please register (otpauth uri): " + otpAuthURL);
        System.out.println("Secret key is " + secret);

        for (Integer i : scratchCodes)
        {
            if (!googleAuthenticator.validateScratchCode(i))
            {
                throw new IllegalArgumentException("An invalid code has been " +
                        "generated: this is an application bug.");
            }
            System.out.println("Scratch code: " + i);
        }
    }

    @Test
    public void authorise()
    {
        GoogleAuthenticatorConfigBuilder gacb =
                new GoogleAuthenticatorConfigBuilder()
                        .setTimeStepSizeInMillis(TimeUnit.SECONDS.toMillis(30))
                        .setWindowSize(5);
        GoogleAuthenticator ga = new GoogleAuthenticator(gacb.build());

        boolean isCodeValid = ga.authorize(SECRET_KEY, VALIDATION_CODE);

        System.out.println("Check VALIDATION_CODE = " + isCodeValid);
    }

    @Test
    public void authoriseUser()
    {
        GoogleAuthenticatorConfigBuilder gacb =
                new GoogleAuthenticatorConfigBuilder()
                        .setTimeStepSizeInMillis(TimeUnit.SECONDS.toMillis(30))
                        .setWindowSize(5)
                        .setCodeDigits(6);
        GoogleAuthenticator ga = new GoogleAuthenticator(gacb.build());

        boolean isCodeValid = ga.authorizeUser("testName", VALIDATION_CODE);

        System.out.println("Check VALIDATION_CODE = " + isCodeValid);
    }

}