package eu.dnetlib.enabling.aas.saml.adapter;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.lang.reflect.UndeclaredThrowableException;
import java.security.KeyPair;

import org.joda.time.DateTime;
import org.junit.BeforeClass;
import org.junit.Test;
import org.opensaml.Configuration;
import org.opensaml.DefaultBootstrap;
import org.opensaml.lite.saml2.core.Issuer;
import org.opensaml.saml2.core.Assertion;
import org.opensaml.saml2.core.Conditions;
import org.opensaml.saml2.core.NameID;
import org.opensaml.saml2.core.Subject;
import org.opensaml.saml2.core.impl.AssertionBuilder;
import org.opensaml.saml2.core.impl.AssertionImpl;
import org.opensaml.saml2.core.impl.ConditionsBuilder;
import org.opensaml.saml2.core.impl.IssuerBuilder;
import org.opensaml.saml2.core.impl.NameIDBuilder;
import org.opensaml.saml2.core.impl.SubjectBuilder;
import org.opensaml.xml.security.credential.BasicCredential;
import org.opensaml.xml.security.credential.Credential;
import org.opensaml.xml.security.credential.UsageType;
import org.opensaml.xml.signature.Signature;
import org.opensaml.xml.signature.SignatureConstants;
import org.opensaml.xml.signature.SignatureValidator;
import org.opensaml.xml.signature.Signer;
import org.opensaml.xml.signature.impl.SignatureImpl;

import pl.edu.icm.yadda.aas.refresher.IExpirationValidator;
import pl.edu.icm.yadda.aas.refresher.IExpirationValidator.ExpirationStatus;
import pl.edu.icm.yadda.aas.refresher.impl.AttributeStatementBasedAssertionRefresher;
import pl.edu.icm.yadda.aas.timesync.impl.LocalDateTimeProviderImpl;

import eu.dnetlib.enabling.aas.utils.SecurityUtils;

/**
 * Assertion adapter test class.
 * @author mhorst
 *
 */
public class AssertionAdapterTest {

	@BeforeClass
	public static void initClass() throws Exception {
//		required by openSAML
		DefaultBootstrap.bootstrap();
//		required by adapters model
		AdapterFactoryRegistry.bootstrap();
	}
	
	@SuppressWarnings("unchecked")
	@Test
	public void testAdaptAssertion() throws Exception {
//		creating source assertion
		String assertionId = "someAssertionId";
		AssertionBuilder builder = new AssertionBuilder();
		Assertion sourceAssertion = builder.buildObject();
		sourceAssertion.setID(assertionId);
		
//		creating adapter
		IAdapterFactory<Assertion, org.opensaml.lite.saml2.core.Assertion> adapterFactory = 
			(IAdapterFactory<Assertion, org.opensaml.lite.saml2.core.Assertion>) AdapterFactoryRegistry.lookup(AssertionImpl.class);
		org.opensaml.lite.saml2.core.Assertion resultAssertion = 
			adapterFactory.newInstance(sourceAssertion);
		assertNotNull(resultAssertion);
		assertEquals(assertionId, resultAssertion.getID());
		
//		checking if can retrieve adapted object
		assertTrue(resultAssertion instanceof IAdapter);
		assertTrue(sourceAssertion == ((IAdapter) resultAssertion).getAdaptedObject());
		
		Issuer issuer = resultAssertion.getIssuer();
		assertNull(issuer);
		
		IssuerBuilder issuerBuilder = new IssuerBuilder();
		sourceAssertion.setIssuer(issuerBuilder.buildObject());
		String issuerId = "someIssuer";
		sourceAssertion.getIssuer().setValue(issuerId);
//		this will throw class cast exception
		try {
			AdapterFactoryRegistry.register(org.opensaml.saml2.core.impl.IssuerImpl.class, 
					null);
			issuer = resultAssertion.getIssuer();
			fail("exception should be thrown when no adapter is registered for Advice");
		} catch(UndeclaredThrowableException e) {
			assertTrue(e.getCause() instanceof AdapterException);
		}
		AdapterFactoryRegistry.register(org.opensaml.saml2.core.impl.IssuerImpl.class, 
				new SAMLObjectGenericAdapterFactory<org.opensaml.saml2.core.Issuer, 
				org.opensaml.lite.saml2.core.Issuer>(
						org.opensaml.lite.saml2.core.Issuer.class));
		issuer = resultAssertion.getIssuer();
		assertNotNull(issuer);
		assertEquals(issuerId, issuer.getValue());
		
//		checking if isSigned() method works properly
		assertFalse(resultAssertion.isSigned());
	}
	
	@SuppressWarnings("unchecked")
	@Test
	public void testCheckingDateTimeStatus() throws Exception {
		int validityTime = 500;
//		creating source assertion containing NotBefore and NotOnOrAfter conditions
		String assertionId = "someAssertionId";
		AssertionBuilder assBuilder = new AssertionBuilder();
		Assertion sourceAssertion = assBuilder.buildObject();
		sourceAssertion.setID(assertionId);
		
		ConditionsBuilder condBuilder = new ConditionsBuilder();
		Conditions conditions = condBuilder.buildObject();
		conditions.setNotBefore(new DateTime(System.currentTimeMillis()));
		conditions.setNotOnOrAfter(new DateTime(System.currentTimeMillis() + validityTime));
		sourceAssertion.setConditions(conditions);
		
//		creating adapter
		IAdapterFactory<Assertion, org.opensaml.lite.saml2.core.Assertion> adapterFactory = 
			(IAdapterFactory<Assertion, org.opensaml.lite.saml2.core.Assertion>) AdapterFactoryRegistry.lookup(AssertionImpl.class);
		org.opensaml.lite.saml2.core.Assertion resultAssertion = 
			adapterFactory.newInstance(sourceAssertion);
		assertNotNull(resultAssertion);
		assertEquals(assertionId, resultAssertion.getID());
		
		AttributeStatementBasedAssertionRefresher expirationValidator = 
			new AttributeStatementBasedAssertionRefresher();
		expirationValidator.setDateTimeProvider(new LocalDateTimeProviderImpl());
		ExpirationStatus status = expirationValidator.validate(resultAssertion);
//		AssertionDateTimeStatus status = AssertionValidatorHelper.checkDateTimeStatus(
//				resultAssertion, new DateTime());
		assertEquals(ExpirationStatus.valid, status);
		Thread.sleep(validityTime);
		status = expirationValidator.validate(resultAssertion);
//		status = AssertionValidatorHelper.checkDateTimeStatus(
//				resultAssertion, new DateTime());
		assertEquals(ExpirationStatus.permanently_expired, status);
	}
	
	@SuppressWarnings("unchecked")
	@Test
	public void testGettingSignature() throws Exception {
//		creating source assertion
		String assertionId = "someAssertionId";
		AssertionBuilder builder = new AssertionBuilder();
		Assertion sourceAssertion = builder.buildObject();
		sourceAssertion.setID(assertionId);
		
//		creating adapter
		IAdapterFactory<Assertion, org.opensaml.lite.saml2.core.Assertion> adapterFactory = 
			(IAdapterFactory<Assertion, org.opensaml.lite.saml2.core.Assertion>) AdapterFactoryRegistry.lookup(AssertionImpl.class);
		org.opensaml.lite.saml2.core.Assertion resultAssertion = 
			adapterFactory.newInstance(sourceAssertion);
		assertNotNull(resultAssertion);
		assertEquals(assertionId, resultAssertion.getID());
		
//		checking if can retrieve adapted object
		assertTrue(resultAssertion instanceof IAdapter);
		assertTrue(sourceAssertion == ((IAdapter) resultAssertion).getAdaptedObject());
		
//		creating signature
		Signature sourceSignature = (Signature) Configuration.getBuilderFactory()
        .getBuilder(Signature.DEFAULT_ELEMENT_NAME)
        .buildObject(Signature.DEFAULT_ELEMENT_NAME);

		Credential signingCredential = getSigningCredential();
		
		sourceSignature.setSigningCredential(signingCredential);
		sourceSignature.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1);
		sourceSignature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
		sourceSignature.setKeyInfo(null);
		sourceAssertion.setSignature(sourceSignature);
		assertNull(sourceAssertion.getDOM());
		Configuration.getMarshallerFactory().getMarshaller(sourceAssertion).marshall(sourceAssertion);
		assertNotNull(sourceAssertion.getDOM());
		Signer.signObject(sourceSignature);
		
//		checking if isSigned() method works properly
		assertTrue(resultAssertion.isSigned());
//		checking if getting signature works properly
		org.opensaml.lite.signature.Signature signature = resultAssertion.getSignature();
		assertNotNull(signature);
		assertTrue(signature instanceof IAdapter);
		assertTrue(((IAdapter)signature).getAdaptedObject() instanceof SignatureImpl);
//		validating signature
		SignatureValidator sigValidator = new SignatureValidator(signingCredential);
		sigValidator.validate((Signature) ((IAdapter)signature).getAdaptedObject());
//		no exception is expected
	}
	
	Credential getSigningCredential() throws Exception {
		BasicCredential cred = new BasicCredential();
		cred.setUsageType(UsageType.SIGNING);
		KeyPair kp = SecurityUtils.generateKeyPair("RSA", 512);
		cred.setPrivateKey(kp.getPrivate());
		cred.setPublicKey(kp.getPublic());
		return cred;
	}
	
	@SuppressWarnings("unchecked")
	@Test
	public void testGettingSubjectNameId() throws Exception {
//		creating source assertion
		String assertionId = "someAssertionId";
		String nameIdValue = "someNameId";
		AssertionBuilder builder = new AssertionBuilder();
		Assertion sourceAssertion = builder.buildObject();
		sourceAssertion.setID(assertionId);
		SubjectBuilder subjectBuilder = new SubjectBuilder();
		Subject subject = subjectBuilder.buildObject();
		NameIDBuilder nameIdBuilder = new NameIDBuilder();
		NameID nameId = nameIdBuilder.buildObject();
		nameId.setValue(nameIdValue);
		subject.setNameID(nameId);
		sourceAssertion.setSubject(subject);
		
//		creating adapter
		IAdapterFactory<Assertion, org.opensaml.lite.saml2.core.Assertion> adapterFactory = 
			(IAdapterFactory<Assertion, org.opensaml.lite.saml2.core.Assertion>) AdapterFactoryRegistry.lookup(AssertionImpl.class);
		org.opensaml.lite.saml2.core.Assertion resultAssertion = 
			adapterFactory.newInstance(sourceAssertion);
		assertNotNull(resultAssertion);
		assertNotNull(resultAssertion.getSubject());
		assertNotNull(resultAssertion.getSubject().getNameID());
		assertEquals(nameIdValue, resultAssertion.getSubject().getNameID().getValue());
		
	}
}
