/***********************************************************
Copyright (C) 2019 VeriSign, Inc.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

http://www.verisign.com/nds/naming/namestore/techdocs.html
 ***********************************************************/

package com.verisign.epp.codec.loginsec.v04;

import java.sql.Date;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Vector;

import com.verisign.epp.codec.gen.EPPCodecTst;
import com.verisign.epp.codec.gen.EPPEncodeDecodeStats;
import com.verisign.epp.codec.gen.EPPLoginCmd;
import com.verisign.epp.codec.gen.EPPResponse;
import com.verisign.epp.codec.gen.EPPResult;
import com.verisign.epp.codec.gen.EPPService;
import com.verisign.epp.codec.gen.EPPTransId;
import com.verisign.epp.exception.EPPException;
import com.verisign.epp.interfaces.EPPApplicationSingle;
import com.verisign.epp.interfaces.EPPCommandException;
import com.verisign.epp.util.TestThread;

import junit.framework.Assert;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

/**
 * Is a unit test of the com.verisign.epp.codec.loginsec.v04 package that
 * includes testing the extentions to the login command and response. The unit
 * test will execute, gather statistics, and output the results of a test of
 * each com.verisign.epp.codec.loginsec.v04 package concrete extension
 * <code>EPPCodecComponent</code>'s.
 */
public class EPPLoginSecTst extends TestCase {

	/**
	 * Handle to the Singleton EPP Application instance (
	 * <code>EPPApplicationSingle</code>)
	 */
	private static EPPApplicationSingle app = EPPApplicationSingle.getInstance();

	/**
	 * Number of unit test iterations to run. This is set in
	 * <code>EPPCodecTst.main</code>
	 */
	static private long numIterations = 1;

	/** Name of configuration file to use for test (default = epp.config). */
	private static String configFileName = "epp.config";

	/**
	 * Creates a new EPPLoginSecTst object.
	 *
	 * @param name
	 *           Name of the test
	 */
	public EPPLoginSecTst(String name) {
		super(name);
	}

	/**
	 * Unit test for the extension to the login command with long current
	 * password.
	 */
	public void testLoginCmdLongPwd() {
		EPPCodecTst.printStart("testLoginCmdLongPwd");

		EPPEncodeDecodeStats commandStats;

		// Services
		Vector services = new Vector();

		// Object services
		EPPService service = new EPPService("obj1", "urn:ietf:params:xml:ns:obj1");
		service.setServiceType(EPPService.OBJ_SERVICE);

		services.addElement(service);

		// Extension services
		Vector extservices = new Vector();
		EPPService extservice = new EPPService("loginSec", "urn:ietf:params:xml:ns:loginSec-0.4");
		extservice.setServiceType(EPPService.EXT_SERVICE);
		extservices.addElement(extservice);

		// TEST - Set long current password
		EPPLoginCmd theCommand = new EPPLoginCmd("ABC-12345", "ClientX", EPPLoginSec.LOGIN_SECURITY_PASSWORD);

		theCommand.setServices(services);
		theCommand.setExtensionServices(extservices);
		theCommand.setVersion("1.0");
		theCommand.setLang("en");

		// Add LoginSec Extension
		EPPLoginSec theExt = new EPPLoginSec(
		      new EPPLoginSecUserAgent("EPP SDK 1.0.0", "Java 11.0.2", "x86_64 Mac OS X 10.11.6"));
		theCommand.addExtension(theExt);

		commandStats = EPPCodecTst.testEncodeDecode(theCommand);
		System.out.println(commandStats);

		EPPCodecTst.printEnd("testLoginCmdLongPwd");
	}

	/**
	 * Unit test for the extension to the login command with long new password.
	 */
	public void testLoginCmdLongNewPwd() {
		EPPCodecTst.printStart("testLoginCmdLongNewPwd");

		EPPEncodeDecodeStats commandStats;

		// Services
		Vector services = new Vector();

		// Object services
		EPPService service = new EPPService("obj1", "urn:ietf:params:xml:ns:obj1");
		service.setServiceType(EPPService.OBJ_SERVICE);

		services.addElement(service);

		// Extension services
		Vector extservices = new Vector();
		EPPService extservice = new EPPService("loginSec", "urn:ietf:params:xml:ns:loginSec-0.1");
		extservice.setServiceType(EPPService.EXT_SERVICE);
		extservices.addElement(extservice);

		EPPLoginCmd theCommand = new EPPLoginCmd("ABC-12345", "ClientX", "shortpassword",
		      EPPLoginSec.LOGIN_SECURITY_PASSWORD);

		theCommand.setServices(services);
		theCommand.setExtensionServices(extservices);
		theCommand.setVersion("1.0");
		theCommand.setLang("en");

		// Add LoginSec Extension
		EPPLoginSec theExt = new EPPLoginSec();
		theExt.setNewPassword("new password that is long");
		theCommand.addExtension(theExt);

		commandStats = EPPCodecTst.testEncodeDecode(theCommand);
		System.out.println(commandStats);

		EPPCodecTst.printEnd("testLoginCmdLongNewPwd");
	}

	/**
	 * Unit test for the extension to the login command with long current and new
	 * password.
	 */
	public void testLoginCmdLongPwdNewPwd() {
		EPPCodecTst.printStart("testLoginCmdLongPwdNewPwd");

		EPPEncodeDecodeStats commandStats;

		// Services
		Vector services = new Vector();

		// Object services
		EPPService service = new EPPService("obj1", "urn:ietf:params:xml:ns:obj1");
		service.setServiceType(EPPService.OBJ_SERVICE);

		services.addElement(service);

		// Extension services
		Vector extservices = new Vector();
		EPPService extservice = new EPPService("loginSec", "urn:ietf:params:xml:ns:loginSec-0.1");
		extservice.setServiceType(EPPService.EXT_SERVICE);
		extservices.addElement(extservice);

		EPPLoginCmd theCommand = new EPPLoginCmd("ABC-12345", "ClientX", EPPLoginSec.LOGIN_SECURITY_PASSWORD,
		      EPPLoginSec.LOGIN_SECURITY_PASSWORD);

		theCommand.setServices(services);
		theCommand.setExtensionServices(extservices);
		theCommand.setVersion("1.0");
		theCommand.setLang("en");

		// Add LoginSec Extension
		EPPLoginSec theExt = new EPPLoginSec(null, "this is a long password", "new password that is still long");
		theCommand.addExtension(theExt);

		commandStats = EPPCodecTst.testEncodeDecode(theCommand);
		System.out.println(commandStats);

		EPPCodecTst.printEnd("testLoginCmdLongPwdNewPwd");
	}

	/**
	 * Unit test for extending login response with expiring password event.
	 */
	public void testExpiringPasswordResponse() {
		EPPCodecTst.printStart("testExpiringPasswordResponse");

		EPPEncodeDecodeStats commandStats;

		EPPTransId theTransId = new EPPTransId("ABC-12345", "54321-XYZ");

		// TEST - Password expiring in a week
		EPPResponse theResponse = new EPPResponse(theTransId);
		theResponse.setResult(EPPResult.SUCCESS);

		EPPLoginSecData loginSecData = new EPPLoginSecData();
		Calendar calendar = new GregorianCalendar();
		calendar.setTime(new Date(System.currentTimeMillis()));
		calendar.add(Calendar.DAY_OF_YEAR, 7);
		EPPLoginSecEvent passwordExpiryEvent = new EPPLoginSecEvent(EventType.PASSWORD, EventLevel.WARNING,
		      calendar.getTime(), "Password expiring in a week");
		loginSecData.addEvent(passwordExpiryEvent);
		theResponse.addExtension(loginSecData);

		commandStats = EPPCodecTst.testEncodeDecode(theResponse);
		System.out.println(commandStats);

		EPPCodecTst.printEnd("testExpiringPasswordResponse");
	}

	/**
	 * Unit test for extending login response with expired password event.
	 */
	public void testExpiredPasswordResponse() {
		EPPCodecTst.printStart("testExpiredPasswordResponse");

		EPPEncodeDecodeStats commandStats;

		EPPTransId theTransId = new EPPTransId("ABC-12345", "54321-XYZ");

		EPPResponse theResponse = new EPPResponse(theTransId);
		theResponse.setResult(EPPResult.AUTHENTICATION_ERROR);

		EPPLoginSecData loginSecData = new EPPLoginSecData();
		Calendar calendar = new GregorianCalendar();
		calendar.setTime(new Date(System.currentTimeMillis()));
		calendar.add(Calendar.DAY_OF_YEAR, -1);
		EPPLoginSecEvent passwordExpiryEvent = new EPPLoginSecEvent(EventType.PASSWORD, EventLevel.ERROR,
		      calendar.getTime(), "Password has expired");
		loginSecData.addEvent(passwordExpiryEvent);
		theResponse.addExtension(loginSecData);

		commandStats = EPPCodecTst.testEncodeDecode(theResponse);
		System.out.println(commandStats);

		EPPCodecTst.printEnd("testExpiredPasswordResponse");
	}

	/**
	 * Unit test for extending login response with all login security event
	 * types.
	 */
	public void testWarningAllResponse() {
		EPPCodecTst.printStart("testWarningAllResponse");

		EPPEncodeDecodeStats commandStats;

		EPPTransId theTransId = new EPPTransId("ABC-12345", "54321-XYZ");

		EPPResponse theResponse = new EPPResponse(theTransId);
		theResponse.setResult(EPPResult.SUCCESS);

		EPPLoginSecData loginSecData = new EPPLoginSecData();

		// Expiring password event
		Calendar calendar = new GregorianCalendar();
		calendar.setTime(new Date(System.currentTimeMillis()));
		calendar.add(Calendar.DAY_OF_YEAR, 7);
		EPPLoginSecEvent theEvent = new EPPLoginSecEvent(EventType.PASSWORD, EventLevel.WARNING, calendar.getTime(),
		      "Password expiring in a week");
		loginSecData.addEvent(theEvent);

		// Expiring certificate event (no description)
		calendar = new GregorianCalendar();
		calendar.setTime(new Date(System.currentTimeMillis()));
		calendar.add(Calendar.DAY_OF_YEAR, 5);
		theEvent = new EPPLoginSecEvent(EventType.CERTIFICATE, EventLevel.WARNING, calendar.getTime(), null);
		loginSecData.addEvent(theEvent);

		// Insecure cipher
		theEvent = new EPPLoginSecEvent(EventType.CIPHER, EventLevel.WARNING, "SSL_RSA_EXPORT_WITH_RC4_40_MD5",
		      "Insecure cipher negotiated");
		loginSecData.addEvent(theEvent);

		// Insecure protocol
		theEvent = new EPPLoginSecEvent(EventType.TLS_PROTOCOL, EventLevel.WARNING, "SSLv3",
		      "Insecure TLS protocol negotiated");
		loginSecData.addEvent(theEvent);

		// Invalid new password
		theEvent = new EPPLoginSecEvent(EventType.NEW_PW, EventLevel.ERROR,
		      "New password does not meet complexity requirements");
		loginSecData.addEvent(theEvent);

		// Failed login statistical event
		theEvent = new EPPLoginSecEvent(EventType.STAT, "failedLogins", EventLevel.WARNING, "100", "P1D",
		      "100 invalid logins over 1 day");
		loginSecData.addEvent(theEvent);

		// Custom security event
		theEvent = new EPPLoginSecEvent(EventType.CUSTOM, "myCustomEvent", EventLevel.WARNING,
		      "A custom login security event occured");
		loginSecData.addEvent(theEvent);

		theResponse.addExtension(loginSecData);

		commandStats = EPPCodecTst.testEncodeDecode(theResponse);
		System.out.println(commandStats);

		EPPCodecTst.printEnd("testWarningAllResponse");
	}

	/**
	 * Execute negative tests of using invalid password with the base login
	 * command as well as with the login security extension. The exact cases
	 * include: <br>
	 * <ol>
	 * <li>Base login password longer than the 16 character maximum.
	 * <li>Base login new password longer than the 16 character maximum.
	 * <li>Login Security Extension with password shorter than the 8 character
	 * minimum.
	 * <li>Login Security Extension new password shorter than the 8 character
	 * minimum.
	 * </ol>
	 */
	public void testNegativePassword() {
		EPPCodecTst.printStart("testNegativePassword");

		// Services
		Vector services = new Vector();

		// Object services
		EPPService service = new EPPService("obj1", "urn:ietf:params:xml:ns:obj1");
		service.setServiceType(EPPService.OBJ_SERVICE);

		services.addElement(service);

		// Extension services
		Vector extservices = new Vector();
		EPPService extservice = new EPPService("loginSec", "urn:ietf:params:xml:ns:loginSec-0.1");
		extservice.setServiceType(EPPService.EXT_SERVICE);
		extservices.addElement(extservice);

		// ---- Base login password longer than the 16 character maximum.
		EPPLoginCmd theCommand = new EPPLoginCmd("ABC-12345", "ClientX", "01234567890123456");

		theCommand.setServices(services);
		theCommand.setExtensionServices(extservices);
		theCommand.setVersion("1.0");
		theCommand.setLang("en");

		try {
			EPPCodecTst.testXMLEncodeDecode(theCommand, null);
			Assert.fail("Base login password longer than the 16 character maximum should have failed");
		}
		catch (EPPException ex) {
			System.out.println("Received expected exception with long password: " + ex);
		}

		// ---- Base login new password longer than the 16 character maximum.
		theCommand = new EPPLoginCmd("ABC-12345", "ClientX", "0123456789012345", "01234567890123456");

		theCommand.setServices(services);
		theCommand.setExtensionServices(extservices);
		theCommand.setVersion("1.0");
		theCommand.setLang("en");

		try {
			EPPCodecTst.testXMLEncodeDecode(theCommand, null);
			Assert.fail("Base login new password longer than the 16 character maximum should have failed");
		}
		catch (EPPException ex) {
			System.out.println("Received expected exception with long new password: " + ex);
		}

		// ---- Login Security Extension with password shorter than the 8
		// character minimum.
		theCommand = new EPPLoginCmd("ABC-12345", "ClientX", EPPLoginSec.LOGIN_SECURITY_PASSWORD,
		      EPPLoginSec.LOGIN_SECURITY_PASSWORD);

		theCommand.setServices(services);
		theCommand.setExtensionServices(extservices);
		theCommand.setVersion("1.0");
		theCommand.setLang("en");

		theCommand.addExtension(new EPPLoginSec(null, "0123456"));

		try {
			EPPCodecTst.testXMLEncodeDecode(theCommand, null);
			Assert.fail("Login Security Extension with password shorter than the 8 character minimum should have failed");
		}
		catch (EPPException ex) {
			System.out.println("Received expected exception with short Login Security Extension password: " + ex);
		}

		// ---- Login Security Extension new password shorter than the 8 character
		// minimum.
		theCommand = new EPPLoginCmd("ABC-12345", "ClientX", EPPLoginSec.LOGIN_SECURITY_PASSWORD,
		      EPPLoginSec.LOGIN_SECURITY_PASSWORD);

		theCommand.setServices(services);
		theCommand.setExtensionServices(extservices);
		theCommand.setVersion("1.0");
		theCommand.setLang("en");

		theCommand.addExtension(new EPPLoginSec(null, "01234567", "0123456"));

		try {
			EPPCodecTst.testXMLEncodeDecode(theCommand, null);
			Assert.fail("Login Security Extension new password shorter than the 8 character minimum should have failed");
		}
		catch (EPPException ex) {
			System.out.println("Received expected exception with short Login Security Extension new password: " + ex);
		}

		EPPCodecTst.printEnd("testNegativePassword");
	}

	/**
	 * JUNIT <code>setUp</code> method, which sets the default client Id to
	 * "theRegistrar" and initializes the <code>EPPNamestoreExtMapFactory</code>
	 * with the <code>EPPCodec</code>.
	 */
	protected void setUp() {
	}

	/**
	 * JUNIT <code>tearDown</code>, which currently does nothing.
	 */
	protected void tearDown() {
	}

	/**
	 * JUNIT <code>suite</code> static method, which returns the tests associated
	 * with <code>EPPLoginSecTst</code>.
	 *
	 * @return Tests to run
	 */
	public static Test suite() {
		EPPCodecTst.initEnvironment();

		TestSuite suite = new TestSuite(EPPLoginSecTst.class);

		String theConfigFileName = System.getProperty("EPP.ConfigFile");
		if (theConfigFileName != null)
			configFileName = theConfigFileName;

		try {
			app.initialize(configFileName);
		}
		catch (EPPCommandException e) {
			e.printStackTrace();

			Assert.fail("Error initializing the EPP Application: " + e);
		}

		// iterations Property
		String numIterProp = System.getProperty("iterations");

		if (numIterProp != null) {
			numIterations = Integer.parseInt(numIterProp);
		}

		return suite;
	}

	/**
	 * Unit test main, which accepts the following system property options:<br>
	 * 
	 * <ul>
	 * <li>iterations Number of unit test iterations to run</li>
	 * <li>validate Turn XML validation on (<code>true</code>) or off (
	 * <code>false</code>). If validate is not specified, validation will be off.
	 * </li>
	 * </ul>
	 *
	 * @param args
	 *           Program arguments
	 */
	public static void main(String[] args) {
		// Number of Threads
		int numThreads = 1;
		String threadsStr = System.getProperty("threads");

		if (threadsStr != null) {
			numThreads = Integer.parseInt(threadsStr);
		}

		// Run test suite in multiple threads?
		if (numThreads > 1) {
			// Spawn each thread passing in the Test Suite
			for (int i = 0; i < numThreads; i++) {
				TestThread thread = new TestThread("EPPLoginSecTst Thread " + i, EPPLoginSecTst.suite());
				thread.start();
			}
		}
		else { // Single threaded mode.
			junit.textui.TestRunner.run(EPPLoginSecTst.suite());
		}
	}

	/**
	 * Sets the number of iterations to run per test.
	 *
	 * @param aNumIterations
	 *           number of iterations to run per test
	 */
	public static void setNumIterations(long aNumIterations) {
		numIterations = aNumIterations;
	}

}
