/*
 * Copyright (c) 2011 ICM Uniwersytet Warszawski All rights reserved.
 * See LICENCE file for licensing information.
 */
package eu.emi.security.authn.x509.helpers.ns;

import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.security.auth.x500.X500Principal;

import eu.emi.security.authn.x509.NamespaceCheckingMode;
import eu.emi.security.authn.x509.ValidationError;
import eu.emi.security.authn.x509.ValidationErrorCode;
import eu.emi.security.authn.x509.impl.X500NameUtils;
import eu.emi.security.authn.x509.proxy.ProxyUtils;

/**
 * Implements namespace policy checking. The class is populated with a namespace policy store
 * which provides the policies. The implementation gets  
 * 
 * 
 * @author K. Benedyczak
 */
public class NamespaceChecker
{
	private boolean namespaceRequired;
	private boolean checkAll;
	private NamespacesStore[] nsStores;

	public NamespaceChecker(NamespaceCheckingMode mode, NamespacesStore pmaStore, 
			NamespacesStore globusStore)
	{
		namespaceRequired = mode.isRequired();
		checkAll = (mode == NamespaceCheckingMode.EUGRIDPMA_AND_GLOBUS || 
				mode == NamespaceCheckingMode.EUGRIDPMA_AND_GLOBUS_REQUIRE);
		int used = 0;
		if (mode.globusEnabled())
			used++;
		if (mode.euGridPmaEnabled())
			used++;
		nsStores = new NamespacesStore[used];
		if (mode.isGlobusFirst())
		{
			nsStores[0] = globusStore;
			if (mode.euGridPmaEnabled())
				nsStores[1] = pmaStore;
		} else
		{
			if (mode.euGridPmaEnabled())
				nsStores[0] = pmaStore;
			if (mode.globusEnabled())
				nsStores[1] = globusStore;
		}
	}
	
	/**
	 * Checks all certificates in the chain whether they are correct w.r.t. namespace policies
	 * which are configured. If the parameter contains any proxy certificates those are ignored.
	 * Self signed certificates in the chain are ignored, so the root CA certificate may be safely 
	 * present in the chain. 
	 * @param chain to be checked
	 * @return list of validation errors
	 */
	public List<ValidationError> check(X509Certificate[] chain)
	{
		if (nsStores.length == 0)
			return Collections.emptyList();
		List<ValidationError> ret = new ArrayList<ValidationError>();
		
		for (int i=0; i<chain.length; i++)
		{
			boolean found = false;
			X500Principal certIssuer = chain[i].getIssuerX500Principal();
			X500Principal certSubject = chain[i].getSubjectX500Principal();
			if (certIssuer.equals(certSubject))
				continue;
			if (ProxyUtils.isProxy(chain[i]))
				continue;
			
			for (NamespacesStore nsStore: nsStores)
			{
				List<NamespacePolicy> policies = nsStore.getPolicies(chain, i);
				if (policies == null || policies.size() == 0)
					continue;
				found = true;
				doCheck(certSubject, policies, ret, i, chain);
				if (!checkAll)
					break;
			}
			if (!found && namespaceRequired)
			{
				ret.add(new ValidationError(chain, i, ValidationErrorCode.nsUndefinedAndRequired,  
						X500NameUtils.getReadableForm(certIssuer)));
			}
		}
		return ret;
	}
	
	private void doCheck(X500Principal subject, List<NamespacePolicy> policies, 
			List<ValidationError> ret, int pos, X509Certificate[] chain)
	{
		boolean permitFound = false;
		StringBuilder policyNames = new StringBuilder();
		for (NamespacePolicy policy: policies)
		{
			policyNames.append(policy.getIdentification()).append(" ");
			if (policy.isSubjectMatching(subject))
			{
				if (!policy.isPermit())
					ret.add(new ValidationError(chain, pos, ValidationErrorCode.nsDeny, 
							X500NameUtils.getReadableForm(subject),							 
							policy.getIdentification()));
				else
					permitFound = true;
			}
		}
		
		if (!permitFound)
		{
			ret.add(new ValidationError(chain, pos, ValidationErrorCode.nsNotAccepted, 
					X500NameUtils.getReadableForm(subject),
					policyNames.toString()));
		}
	}
}
