/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package eu.dnetlib.espas.pep;

import java.io.File;
import java.io.FileOutputStream;
import java.rmi.RemoteException;
import java.util.Collection;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMText;
import org.apache.axiom.om.impl.llom.util.AXIOMUtil;
import org.apache.axis2.client.Options;
import org.apache.axis2.client.ServiceClient;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.ConfigurationContextFactory;
import org.apache.axis2.transport.http.HTTPConstants;
import org.apache.axis2.transport.http.HttpTransportProperties;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.springframework.core.io.Resource;
import org.wso2.carbon.identity.entitlement.stub.EntitlementServiceException;
import org.wso2.carbon.identity.entitlement.stub.EntitlementServiceStub;

/**
 * The AuthenticationPEP implements a policy enforcement point that is used primarily by the DownloaManager to facilitate the authentication of requested download activities. Submitted requests are
 * evaluated against the policies maintained by the ESPAS policy registry and evaluation outcomes are used for checking whether specified actions are permitted or not by the Data Providers. The
 * provided implementation is thread safe.
 *
 * @author gathanas
 */
public class AuthenticationPEP {

    private static final Logger _logger = Logger.getLogger(AuthenticationPEP.class);

    private EntitlementServiceStub entServiceStub;

    private Resource trustStore = null;
    private String trustStorePassword = null;
    private String serverUsername = null;
    private String serverPassword = null;
    private String serverUrl = null;
    private long connectionTimeout = 0;

    public void initEntitlementService() throws Exception {
        File tempFile = File.createTempFile("trs", null);
        FileOutputStream fos = new FileOutputStream(tempFile);
        tempFile.deleteOnExit();

        IOUtils.copy(this.trustStore.getInputStream(), fos);
        fos.close();

        System.setProperty("javax.net.ssl.trustStore", tempFile.getCanonicalPath());
        System.setProperty("javax.net.ssl.trustStorePassword", this.trustStorePassword);

        ConfigurationContext confContx = ConfigurationContextFactory.createConfigurationContextFromFileSystem(null, null);

        HttpTransportProperties.Authenticator httpAuthenticator = new HttpTransportProperties.Authenticator();
        httpAuthenticator.setUsername(this.serverUsername);
        httpAuthenticator.setPassword(this.serverPassword);
        httpAuthenticator.setPreemptiveAuthentication(true);

        entServiceStub = new EntitlementServiceStub(confContx, this.serverUrl);
        ServiceClient srv2client = entServiceStub._getServiceClient();
        Options _options = srv2client.getOptions();

        _options.setManageSession(true);
        _options.setProperty(HTTPConstants.COOKIE_STRING, null);
        _options.setProperty(HTTPConstants.AUTHENTICATE, httpAuthenticator);
        _options.setManageSession(true);
        _options.setTimeOutInMilliSeconds(this.connectionTimeout);
        srv2client.setOptions(_options);

        _logger.info("Entitlement service initialized succesfully");
    }

    /**
     * Checks the conformance of a list of requested objects against the specified policies and returns a list of corresponding responses.
     *
     */
    public synchronized PEPResponseMap isPermitedRequest(Collection<String> requestedObs, String userId, String actionName, String[] environment) throws RemoteException {
        _logger.info("Evaluating request for [" + requestedObs.size() + ", " + userId + ", " + actionName + "]");
        PEPResponseMap responseMap = new PEPResponseMap();
        if (entServiceStub != null)
            for (String requestedResource : requestedObs)
                try {
                    String getDecisionOutcome = entServiceStub.getDecisionByAttributes(userId, requestedResource, actionName, environment);
                    _logger.trace("Evaluation of [" + requestedObs.size() + ", " + userId + ", " + actionName + "] returned :" + getDecisionOutcome);
                    String[] pepRespopnse = this.getPDPResults(getDecisionOutcome);
                    PEPResponse pepResponse = new PEPResponse(requestedResource, pepRespopnse[1], pepRespopnse[0].contains("Permit") ? true : false);
                    responseMap.addResponse(requestedResource, pepResponse);
                } catch (XMLStreamException ex) {
                    _logger.error(null, ex);
                    continue;
                } catch (EntitlementServiceException ex) {
                    _logger.error(null, ex);
                    continue;
                } catch (Exception ex) {
                    _logger.error(null, ex);
                    continue;
                }
        return responseMap;
    }

    public synchronized boolean isPermitedRequest(String requestedOb, String userId, String actionName, String[] environment) throws RemoteException, EntitlementServiceException, Exception {
        _logger.info("Evaluating request for [" + requestedOb + ", " + userId + ", " + actionName + "]");
        if (entServiceStub != null) {
            String outcome = entServiceStub.getDecisionByAttributes(userId, requestedOb, actionName, environment);
            _logger.info("Evaluation of [" + requestedOb + ", " + userId + ", " + actionName + "] returned :" + outcome);
            return outcome.contains("Permit") ? true : false;
        }
        return false;
    }

    public synchronized boolean isPermitedRequest(String request) throws RemoteException, EntitlementServiceException, Exception {

        _logger.info("Evaluating request " + request);
        if (entServiceStub != null) {
            String outcome = entServiceStub.getDecision(request);
            _logger.info("Evaluation of [" + request + "] returned :" + outcome);
//            XMLStreamReader streamReader = XMLInputFactory.newInstance().createXMLStreamReader(new StringReader(outcome));
            return outcome.contains("Permit") ? true : false;
        }
        return false;
    }

    public Resource getTrustStore() {
        return trustStore;
    }

    public void setTrustStore(Resource trustStore) {
        this.trustStore = trustStore;
    }

    public String getTrustStorePassword() {
        return trustStorePassword;
    }

    public void setTrustStorePassword(String trustStorePassword) {
        this.trustStorePassword = trustStorePassword;
    }

    public String getServerUsername() {
        return serverUsername;
    }

    public void setServerUsername(String serverUsername) {
        this.serverUsername = serverUsername;
    }

    public String getServerPassword() {
        return serverPassword;
    }

    public void setServerPassword(String serverPassword) {
        this.serverPassword = serverPassword;
    }

    public String getServerUrl() {
        return serverUrl;
    }

    public void setServerUrl(String serverUrl) {
        this.serverUrl = serverUrl;
    }

    public long getConnectionTimeout() {
        return connectionTimeout;
    }

    public void setConnectionTimeout(long connectionTimeout) {
        this.connectionTimeout = connectionTimeout;
    }
    
    
   /* Parses the PDP response and extracts the returned values
     */
    protected String[] getPDPResults(String response) throws XMLStreamException{
        String[] resultMsg = new String[2];
        OMElement omElement= AXIOMUtil.stringToOM(response);
        if(omElement!=null && omElement.getChildElements().hasNext()){
//            retrieve decision value
            OMElement resultOM = ((OMElement)omElement.getFirstChildWithName(new QName("urn:oasis:names:tc:xacml:3.0:core:schema:wd-17","Result")));
            
            OMElement decisionOM=resultOM.getFirstChildWithName(new QName("urn:oasis:names:tc:xacml:3.0:core:schema:wd-17","Decision"));
            resultMsg[0] = decisionOM!=null?((OMText)decisionOM.getFirstOMChild()).getText():"";
//            retrieve first advice msg
            OMElement advice = (OMElement)resultOM.getFirstChildWithName(new QName("urn:oasis:names:tc:xacml:3.0:core:schema:wd-17","AssociatedAdvice"));
            if(advice!=null){
                OMElement attributeAssignOM = (OMElement)advice.getFirstElement().getFirstChildWithName(new QName("urn:oasis:names:tc:xacml:3.0:core:schema:wd-17","AttributeAssignment"));
                resultMsg[1]=attributeAssignOM!=null?((OMText)attributeAssignOM.getFirstOMChild()).getText():"";
            }
            else
                resultMsg[1]="";
           }
        return resultMsg;
    }

}
