/*
 *     Copyright (C) 2015 ASIP Santé
 */
package fr.asipsante.mss.clientcxfmss.spring;

import fr.asipsante.mss.connectkit.AbstractSamlAuthenticationManager;
import fr.asipsante.mss.connectkit.paos.AbstractSaml2Driver;
import fr.asipsante.mss.connectkit.paos.Saml2ProtocolException;
import fr.asipsante.mss.connectkit.connection.ConnectionUtil;
import fr.asipsante.mss.connectkit.connection.HttpsConnectionFactory;
import fr.asipsante.mss.connectkit.connection.SSLSecurityException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpCookie;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import javax.net.ssl.HttpsURLConnection;
import javax.xml.namespace.QName;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.binding.soap.interceptor.SoapInterceptor;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.PhaseInterceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;

/**
 * Cet interceptor CXF détecte la requête d'authentification (AuthnRequest). Si la requête d'authentification est présente,
 * alors le processus d'authentification s'exécute.
 * @author ASIP Santé
 */
public class PAOSInInterceptor extends AbstractSamlAuthenticationManager implements PhaseInterceptor<SoapMessage>,SoapInterceptor {

    protected AbstractSaml2Driver getSamlStrategy() {
        return samlStrategy;
    }

    protected HttpsConnectionFactory getConnFactory(){
        return this.connFactory;
    }
    
    @Override
    public Set<URI> getRoles() {
        return Collections.EMPTY_SET;
    }

    @Override
    public Set<QName> getUnderstoodHeaders() {
        HashSet<QName> headerSet=new HashSet<>();
        headerSet.add(new QName("urn:liberty:paos:2003-08", "Request"));
        headerSet.add(new QName("urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp","Request"));
        return headerSet;
    }
    
    private AbstractSaml2Driver samlStrategy;
    private HttpsConnectionFactory connFactory;

    /**
     * @param connFactory stratégie HTTPS à utiliser pour la liaison SSL lors des séquences d'authentification.
     */
    public void setConnFactory(HttpsConnectionFactory connFactory) {
        this.connFactory = connFactory;
        this.connFactory.addHttpHeaders(computeHeaderset());
    }
    private ConnectionUtil connUtil = new ConnectionUtil();
    private String protocolVersion="TLSv1";//jamais de version antérieure tq SSLv3 ou SSLv2!!!

    public void setProtocolVersion(String protocolVersion) {
        this.protocolVersion = protocolVersion;
    }

    /**
     * @param samlStrategy variante du protocole d'authentification SAML2 PAOS à utiliser lors des échanges. 
     */
    public void setSamlStrategy(AbstractSaml2Driver samlStrategy) {
        this.samlStrategy = samlStrategy;
    }
    
    public PAOSInInterceptor() {
        LOGGER.debug("Created");
    }

    public void handleMessage(SoapMessage t) throws Fault {
        try (InputStream inStr = t.getContent(InputStream.class);
                ByteArrayOutputStream baos = new ByteArrayOutputStream();) {
            loadAllResponse(inStr, baos);
            if(LOGGER.isDebugEnabled()){
                LOGGER.debug("Stream content: \n"+baos.toString("utf-8"));
            }
            
            byte[] reponseData = baos.toByteArray();
            Node authnRequest = samlStrategy.lookForAuthnRequest(reponseData);
            if(authnRequest==null){
                //OK, on est authentifié, passons à la suite
                LOGGER.debug("Authentifié => rien à faire");
                t.setContent(InputStream.class, new ByteArrayInputStream((reponseData)));
            }else{
                Map<String,List<String>> headerMap = (Map<String,List<String>>)t.get(Message.PROTOCOL_HEADERS);
                String address = t.getExchange().getEndpoint().getEndpointInfo().getAddress();
                HttpCookie sessionId=samlStrategy.extractJSessionId(headerMap, new URI(address));
                
                LOGGER.debug(headerMap.toString());
                URL assertionConsumer=samlStrategy.getAssertionConsumerURL(authnRequest);
                LOGGER.debug("Assertion consumer: "+assertionConsumer);
                
                authenticate(reponseData, protocolVersion, assertionConsumer, sessionId);
                
                PAOSOutInterceptor.OutputStreamTee copieOutmessage=t.getExchange().getOutMessage().getContent(PAOSOutInterceptor.OutputStreamTee.class);
                byte[] messageData=copieOutmessage.getData();
                HttpsURLConnection connection=connFactory.createHttpsConnection(new URL(address), protocolVersion);
                connUtil.addCookie(connection, sessionId);
                connUtil.sendPost(connection, new ByteArrayInputStream(messageData));
                byte[] reponseMetier=connUtil.getResponse(connection);
                LOGGER.debug("Nouvelle réponse métier: "+new String(reponseMetier));
                t.setContent(InputStream.class, new ByteArrayInputStream(reponseMetier));
            }
        } catch (URISyntaxException | SSLSecurityException | Saml2ProtocolException | IOException e) {
            throw new Fault("PAOS.interceptor.failure", ResourceBundle.getBundle("message"), e);
        }
    }

    private void loadAllResponse(final InputStream inStr, final ByteArrayOutputStream baos) throws IOException {
        byte[] buffer = new byte[4048];
        int nb = inStr.read(buffer);
        while (nb >= 0) {
            baos.write(buffer, 0, nb);
            nb = inStr.read(buffer);
        }
        inStr.close();
    }

    private static final Logger LOGGER = LoggerFactory.getLogger(PAOSInInterceptor.class);

    public void handleFault(SoapMessage t) {
    }

    public Set<String> getAfter() {
        return Collections.EMPTY_SET;
    }

    public Set<String> getBefore() {
        return Collections.EMPTY_SET;
    }

    public String getId() {
        return toString();
    }

    public String getPhase() {
        return "user-stream";
    }

    public Collection<PhaseInterceptor<? extends Message>> getAdditionalInterceptors() {
        return Collections.EMPTY_SET;
    }

}
