package eu.dnetlib.enabling.aas.security;

import org.opensaml.lite.common.SAMLObject;
import org.opensaml.lite.saml2.core.EncryptedElementType;
import org.opensaml.lite.signature.Signature;
import org.opensaml.saml2.core.EncryptedAssertion;
import org.opensaml.saml2.core.EncryptedAttribute;
import org.opensaml.saml2.core.EncryptedID;
import org.opensaml.saml2.core.NewEncryptedID;
import org.opensaml.saml2.encryption.Decrypter;
import org.opensaml.xml.encryption.DecryptionException;
import org.opensaml.xml.encryption.InlineEncryptedKeyResolver;
import org.opensaml.xml.security.CriteriaSet;
import org.opensaml.xml.security.SecurityException;
import org.opensaml.xml.security.credential.Credential;
import org.opensaml.xml.security.keyinfo.StaticKeyInfoCredentialResolver;
import org.opensaml.xml.signature.SignatureTrustEngine;

import pl.edu.icm.yadda.aas.keystore.IInternalKeyStore;
import pl.edu.icm.yadda.aas.security.ISecurityFacade;
import pl.edu.icm.yadda.aas.security.SecurityFacadeException;
import eu.dnetlib.enabling.aas.saml.adapter.AdapterException;
import eu.dnetlib.enabling.aas.saml.adapter.AdapterFactoryRegistry;
import eu.dnetlib.enabling.aas.saml.adapter.IAdapter;
import eu.dnetlib.enabling.aas.saml.adapter.IAdapterFactory;

/**
 * openSAML based security facade.
 * 
 * @author mhorst
 *
 */
public class DNetSecurityFacade implements ISecurityFacade<CriteriaSet> {

	/**
	 * openSAML signature trust engine.
	 */
	private SignatureTrustEngine signatureTrustEngine;
	
	/**
	 * Internal key store holding private key for decryption.
	 */
	private IInternalKeyStore<Credential> internalKeyStore;
	

	/* (non-Javadoc)
	 * @see pl.edu.icm.yadda.aas.security.ISecurityFacade#decrypt(org.opensaml.lite.saml2.core.EncryptedElementType)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public SAMLObject decrypt(EncryptedElementType encElement)
			throws SecurityFacadeException {
//		decryption is discussed in:
//		https://spaces.internet2.edu/display/OpenSAML/OSTwoUserManJavaXMLEncryption
//		currently decrypting using the 1) approach: key from local keystore
		if (encElement!=null) {
			if (encElement instanceof IAdapter) {
				StaticKeyInfoCredentialResolver skicr = new StaticKeyInfoCredentialResolver(
						  internalKeyStore.getInternalEncryptionCredential());
				Decrypter samlDecrypter = new Decrypter(null, 
						skicr, new InlineEncryptedKeyResolver());
				org.opensaml.common.SAMLObject resultCandidate = null;
				org.opensaml.saml2.core.EncryptedElementType decrCandidate = 
					(org.opensaml.saml2.core.EncryptedElementType) 
					((IAdapter)encElement).getAdaptedObject();
				try {
					if (decrCandidate instanceof EncryptedAssertion) {
						resultCandidate = samlDecrypter.decrypt(
								(EncryptedAssertion) decrCandidate);
					} else if (decrCandidate instanceof EncryptedAttribute) {
						resultCandidate = samlDecrypter.decrypt(
								(EncryptedAttribute) decrCandidate);
					} else if (decrCandidate instanceof EncryptedID) {
						resultCandidate = samlDecrypter.decrypt(
								(EncryptedID) decrCandidate);
					} else if (decrCandidate instanceof NewEncryptedID) {
						resultCandidate = samlDecrypter.decrypt(
								(NewEncryptedID) decrCandidate);
					} else {
						throw new SecurityFacadeException("unsupported encrypted object type: " +
								decrCandidate.getClass().getCanonicalName());
					}
				} catch (DecryptionException e) {
					throw new SecurityFacadeException("exception occured when decrypting object!" ,e);
				}
//				adopting openSAML object to the SAML-lite model
				if (resultCandidate!=null) {
					try {
						IAdapterFactory adapterFactory = AdapterFactoryRegistry.lookup(
								resultCandidate.getClass());
						if (adapterFactory!=null) {
							return adapterFactory.newInstance(resultCandidate);
						} else {
							throw new SecurityFacadeException("no adapter factory found for class " +
									resultCandidate.getClass().getCanonicalName());
						}
					} catch (AdapterException e) {
						throw new SecurityFacadeException("unable to encapsulate decrypted object " +
								"with proxying adapter!", e);
					}
				} else {
					throw new SecurityFacadeException("got null object after decryption!");
				}
				
			} else {
				throw new SecurityFacadeException("cannot perform decryption! " +
						"Not an Adapter instance!");
			}
		} else {
			throw new SecurityFacadeException("cannot decrypt null object!");
		}
	}

	/* (non-Javadoc)
	 * @see pl.edu.icm.yadda.aas.security.ISecurityFacade#verifySignature(org.opensaml.lite.signature.Signature, org.opensaml.lite.security.CriteriaSet)
	 */
	@Override
	public boolean verifySignature(Signature signature,
			CriteriaSet trustBasisCriteria) throws SecurityFacadeException {
		try {
			return signatureTrustEngine.validate(
					extractSignature(signature), trustBasisCriteria);
		} catch (SecurityException e) {
			throw new SecurityFacadeException(
					"exception occured when validating signature!", e);
		}
	}

	/**
	 * Extracts openSAML signature object from adapter.
	 * @param source
	 * @return openSAML signature object
	 * @throws SecurityFacadeException 
	 */
	@SuppressWarnings("unchecked")
	protected org.opensaml.xml.signature.Signature extractSignature(Signature source) 
		throws SecurityFacadeException {
		if (source!=null) {
			if (source instanceof IAdapter) {
				return (org.opensaml.xml.signature.Signature) 
				((IAdapter)source).getAdaptedObject();
			} else {
				throw new SecurityFacadeException("cannot extract openSAML signature! " +
						"Not an adapter instance of SAML-lite signature!");
			}
		} else {
			return null;
		}
	}
	
	/**
	 * Sets openSAML signature trust engine.
	 * @param signatureTrustEngine
	 */
	public void setSignatureTrustEngine(SignatureTrustEngine signatureTrustEngine) {
		this.signatureTrustEngine = signatureTrustEngine;
	}

	/**
	 * Sets internal keystore.
	 * @param internalKeyStore
	 */
	public void setInternalKeyStore(IInternalKeyStore<Credential> internalKeyStore) {
		this.internalKeyStore = internalKeyStore;
	}

}
