/*
 * Copyright (c) 2011-2012 ICM Uniwersytet Warszawski All rights reserved.
 * See LICENCE file for licensing information.
 * 
 * Parts of this class are derived from the glite.security.util-java module, 
 * copyrighted as follows:
 *
 * Copyright (c) Members of the EGEE Collaboration. 2004. See
 * http://www.eu-egee.org/partners/ for details on the copyright holders.
 */
package eu.emi.security.authn.x509.proxy;

import java.io.IOException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.List;

import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1Encoding;
import org.bouncycastle.asn1.pkcs.Attribute;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;

import eu.emi.security.authn.x509.helpers.JavaAndBCStyle;
import eu.emi.security.authn.x509.helpers.proxy.DraftRFCProxyCertInfoExtension;
import eu.emi.security.authn.x509.helpers.proxy.ProxyAddressRestrictionData;
import eu.emi.security.authn.x509.helpers.proxy.ProxyCertInfoExtension;
import eu.emi.security.authn.x509.helpers.proxy.ProxyHelper;
import eu.emi.security.authn.x509.helpers.proxy.ProxySAMLExtension;
import eu.emi.security.authn.x509.helpers.proxy.ProxyTracingExtension;
import eu.emi.security.authn.x509.helpers.proxy.RFCProxyCertInfoExtension;
import eu.emi.security.authn.x509.impl.CertificateUtils;

/**
 * A class to get the information from the proxy certificate request.
 * 
 * @author J. Hahkala
 * @author K. Benedyczak
 */
public class ProxyCSRInfo 
{
	static 
	{
		CertificateUtils.configureSecProvider();
	}

	private PKCS10CertificationRequest csr;
	
	private List<CertificateExtension> extensions = new ArrayList<CertificateExtension>();
	private String samlAssertion;
	private ProxyCertInfoExtension proxyExt;
	private String proxyExtOid;
	private String tracingSubject;
	private String tracingIssuer;
	private String[][] sourceRestrictions;
	private String[][] targetRestrictions;

	/**
	 * Generates new instance of this class using the Certificate Signing Request
	 * @param csr certificate signing request 
	 * @throws CertificateException if the Certificate Signing Request is invalid
	 */
	public ProxyCSRInfo(PKCS10CertificationRequest csr) throws CertificateException 
	{
		this.csr = csr;
		try
		{
			parseRequestedExtensions();
		} catch (IOException e)
		{
			throw new CertificateException("The CSR can not be parsed as a Proxy CSR", e);
		}
	}

	/**
	 * The type of the proxy certificate requested is returned or null if can not be determined. In principle
	 * the null response means that the DN is not a valid LEGACY proxy DN, and that either RFC or DRAFT 
	 * proxy should be generated.
	 * @return the proxy type
	 */
	public ProxyType getProxyType() 
	{
		if (proxyExtOid != null && proxyExtOid.equals(RFCProxyCertInfoExtension.RFC_EXTENSION_OID))
			return ProxyType.RFC3820;
		if (proxyExtOid != null && proxyExtOid.equals(DraftRFCProxyCertInfoExtension.DRAFT_EXTENSION_OID))
			return ProxyType.DRAFT_RFC;
		
		String value;
		try 
		{
			value = getLastCN();
		} catch (IllegalArgumentException e) //empty or wrong subject
		{
			value = "";
		} catch (IOException e)
		{
			throw new IllegalArgumentException("The CSR can not be parsed", e);
		}
		if ("proxy".equals(value.toLowerCase())
				|| "limited proxy".equals(value.toLowerCase()))
			return ProxyType.LEGACY;
		return null;
	}

	private String getLastCN() throws IllegalArgumentException, IOException
	{
		byte[] subject = csr.getSubject().getEncoded(ASN1Encoding.DER);
		X500Name withDefaultStyle = X500Name.getInstance(subject);
		JavaAndBCStyle style = new JavaAndBCStyle();
		return ProxyHelper.getLastCN(X500Name.getInstance(style, withDefaultStyle));
	}
	
	/**
	 * Used to check whether the Certificate Signing Request is for a limited proxy or not.
	 * @return null if not set
	 */
	public Boolean isLimited() 
	{
		ProxyPolicy policy = getPolicy();
		if (policy != null)
		{
			return ProxyPolicy.LIMITED_PROXY_OID.equals(policy.getPolicyOID());
		} else 
		{
			String value;
			try
			{
				value = getLastCN();
			} catch (IllegalArgumentException e) //empty or wrong subject
			{
				value = "";
			} catch (IOException e)
			{
				throw new IllegalArgumentException("The CSR can not be parsed", e);
			}
			if (value.toLowerCase().equals("proxy"))
				return false;
			else if ("limited proxy".equals(value.toLowerCase()))
				return true;
			return null;
		}
	}

	/**
	 * Gets the requested RFC proxy extension policy OID and octets of the
	 * policy. See RFC3820. Policy can be null in case the OID in it self
	 * defines the behavior, like with "inherit all" policy or
	 * "independent" policy.
	 * @return the requested policy or null if not set 
	 */
	public ProxyPolicy getPolicy()
	{
		if (proxyExt == null)
			return null;
		return proxyExt.getPolicy();
	}

	/**
	 * Returns an requested URL of the proxy tracing issuer.
	 * 
	 * @return The proxy tracing issuer URL in String format, 
	 * or null if was not requested.
	 */
	public String getProxyTracingIssuer() 
	{
		return tracingIssuer; 
	}

	/**
	 * Returns a requested URL of the proxy tracing subject.
	 * @return The proxy tracing subject URL in String format, 
	 * or null if was not requested.
	 */
	public String getProxyTracingSubject()
	{
		return tracingSubject;
	}

	/**
	 * Returns the SAML extension from the certificate chain.
	 * 
	 * @return The SAML assertion in String format or null if not set
	 */
	public String getSAMLExtension()
	{
		return samlAssertion;
	}

	/**
	 * Returns the proxy path length limit set in the Certificate Signing Request.
	 * Returns an Integer.MAX_VALUE value if length is set to be unlimited.
	 * @return the requested proxy path length.
	 */
	public Integer getProxyPathLimit()
	{
		if (proxyExt == null)
			return Integer.MAX_VALUE;
		return proxyExt.getProxyPathLimit();
	}

	/**
	 * Gets the proxy source restriction data from the Certificate Signing Request. 
	 * The returned array has as the first item the array of allowed namespaces 
	 * and as the second item the array of excluded namespaces.
	 * @return null if the extension was not set 
	 */
	public String[][] getProxySourceRestrictions()
	{
		return sourceRestrictions;
	}

	/**
	 * Gets the proxy target restriction data from the Certificate Signing Request. 
	 * The returned array has as the first item the array of allowed namespaces 
	 * and as the second item the array of excluded namespaces. 
	 * @return null if the extension was not set 
	 */
	public String[][] getProxyTargetRestrictions()
	{
		return targetRestrictions;
	}
	
	
	private void parseRequestedExtensions() throws IOException
	{
		Attribute[] attrs = csr.getAttributes();
		if (attrs == null)
			return;
		for (Attribute attr: attrs)
		{
			if (PKCSObjectIdentifiers.pkcs_9_at_extensionRequest.getId().equals(
					attr.getAttrType().getId()))
			{
				if (attr.getAttrValues().size() == 0)
					continue;
				ASN1Encodable req = attr.getAttrValues().getObjectAt(0);
				CertificateExtension ext = new CertificateExtension(req.toASN1Primitive().getEncoded(ASN1Encoding.DER));
				handleRequestedExtension(ext);
			}
		}
	}
	
	private void handleRequestedExtension(CertificateExtension ext) throws IOException
	{
		String oid = ext.getOid();
		byte[] val = ext.getValue().toASN1Primitive().getEncoded(ASN1Encoding.DER);
		if (oid.equals(DraftRFCProxyCertInfoExtension.DRAFT_EXTENSION_OID))
		{
			proxyExtOid = oid;
			proxyExt = new DraftRFCProxyCertInfoExtension(val);
		} else if (oid.equals(RFCProxyCertInfoExtension.RFC_EXTENSION_OID))
		{
			proxyExtOid = oid;
			proxyExt = new RFCProxyCertInfoExtension(val);
		} else if (oid.equals(ProxySAMLExtension.LEGACY_SAML_OID) || 
				oid.equals(ProxySAMLExtension.SAML_OID))
		{
			samlAssertion = new ProxySAMLExtension(val).getSAML();
		} else if (oid.equals(ProxyTracingExtension.PROXY_TRACING_ISSUER_EXTENSION_OID))
		{
			tracingIssuer = new ProxyTracingExtension(val).getURL();
		} else if (oid.equals(ProxyTracingExtension.PROXY_TRACING_SUBJECT_EXTENSION_OID))
		{
			tracingSubject = new ProxyTracingExtension(val).getURL();
		} else if (oid.equals(ProxyAddressRestrictionData.SOURCE_RESTRICTION_OID))
		{
			sourceRestrictions = new String[2][];
			sourceRestrictions[0] = new ProxyAddressRestrictionData(val).getPermittedAddresses();
			sourceRestrictions[1] = new ProxyAddressRestrictionData(val).getExcludedAddresses();
		} else if (oid.equals(ProxyAddressRestrictionData.TARGET_RESTRICTION_OID))
		{
			targetRestrictions = new String[2][];
			targetRestrictions[0] = new ProxyAddressRestrictionData(val).getPermittedAddresses();
			targetRestrictions[1] = new ProxyAddressRestrictionData(val).getExcludedAddresses();
		} else
		{
			extensions.add(ext);
		}
	}
}
