/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2004 Bull S.A.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id: Csiv2ClientInterceptor.java 15428 2008-10-07 11:20:29Z sauthieg $
 * --------------------------------------------------------------------------
 */
package org.ow2.jonas.security.iiop;

import java.io.IOException;
import java.io.UnsupportedEncodingException;

import org.omg.CORBA.Any;
import org.omg.CORBA.BAD_PARAM;
import org.omg.CSI.AuthorizationElement;
import org.omg.CSI.EstablishContext;
import org.omg.CSI.GSS_NT_ExportedNameHelper;
import org.omg.CSI.IdentityToken;
import org.omg.CSI.SASContextBody;
import org.omg.CSI.SASContextBodyHelper;
import org.omg.CSIIOP.CompoundSecMech;
import org.omg.CSIIOP.CompoundSecMechList;
import org.omg.CSIIOP.CompoundSecMechListHelper;
import org.omg.CSIIOP.EstablishTrustInClient;
import org.omg.CSIIOP.IdentityAssertion;
import org.omg.CSIIOP.TAG_CSI_SEC_MECH_LIST;
import org.omg.GSSUP.InitialContextToken;
import org.omg.GSSUP.InitialContextTokenHelper;
import org.omg.IOP.Codec;
import org.omg.IOP.SecurityAttributeService;
import org.omg.IOP.ServiceContext;
import org.omg.IOP.TaggedComponent;
import org.omg.IOP.CodecPackage.FormatMismatch;
import org.omg.IOP.CodecPackage.InvalidTypeForEncoding;
import org.omg.IOP.CodecPackage.TypeMismatch;
import org.omg.PortableInterceptor.ClientRequestInfo;
import org.omg.PortableInterceptor.ClientRequestInterceptor;
import org.omg.PortableInterceptor.ForwardRequest;

import org.ow2.carol.util.csiv2.gss.GSSHelper;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;

/**
 * SAS context interceptor on client side.
 * @see Csiv2 spec : A client security service (CSS) is the security service
 *      associated with the ORB that is used by the client to invoke the target
 *      object.
 * @see client state machine (fig 16-3)
 * @see Common Secure Interoperability V2 Specification (July 23,2001)
 * @author Florent Benoit
 */
public class Csiv2ClientInterceptor extends org.omg.CORBA.LocalObject implements ClientRequestInterceptor {

    /**
     * Name
     */
    private static final String NAME = "Csiv2ClientInterceptor";

    /**
     * Codec to use
     */
    private Codec codec = null;

    /**
     * Logger to use
     */
    private Logger logger = null;

    /**
     * Logger details (On catching exception)
     */
    private Logger loggerDetails = null;

    /**
     * Constructor
     * @param codec used for encoding any objects
     * @param logger used for logging useful information
     * @param loggerDetails for all information (useless for most time :)
     */
    public Csiv2ClientInterceptor(Codec codec, Logger logger, Logger loggerDetails) {
        this.codec = codec;
        this.logger = logger;
        this.loggerDetails = loggerDetails;
    }

    /**
     * Indicates to the interceptor that an exception occurred.  Allows
     * an Interceptor to query the exception's information before it is
     * thrown to the client.
     * @param ri Information about the current request being intercepted.
     * @exception ForwardRequest If thrown, indicates to the ORB that a
     *     retry of the request should occur with the new object given in
     *     the exception.
     */
    public void receive_exception(ClientRequestInfo ri) throws ForwardRequest {

    }

    /**
     * Allows an Interceptor to query the information available when a
     * request results in something other than a normal reply or an
     * exception.
     * @param ri Information about the current request being intercepted.
     * @exception ForwardRequest If thrown, indicates to the ORB that a
     *     retry of the request should occur with the new object given in
     *     the exception.
     */
    public void receive_other(ClientRequestInfo ri) throws ForwardRequest {

    }

    /**
     * Allows an Interceptor to query the information on a reply after it
     * is returned from the server and before control is returned to the
     * client.
     * <p>
     * @param ri Information about the current request being intercepted.
     */
    public void receive_reply(ClientRequestInfo ri) {

    }

    /**
     * Allows an Interceptor to query information during a Time-Independent
     * Invocation (TII) polling get reply sequence.
     * @param ri Information about the current request being intercepted.
     */
    public void send_poll(ClientRequestInfo ri) {

    }

    /**
     * Need to send an establish context as described in the CSS state machine
     * Compliance with level 0, so stateless context
     * @see fig 16-3 of spec. [109] <br>
     */
    public void send_request(ClientRequestInfo ri) throws ForwardRequest {

        // Is there a TAG_CSI_SEC_MECH tagged component in the request ?
        TaggedComponent taggedComponent = null;
        try {
            taggedComponent = ri.get_effective_component(TAG_CSI_SEC_MECH_LIST.value);
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "There is a TAG_CSI_SEC_MECH_LIST tagged component");
            }

        } catch (BAD_PARAM e) {
            if (loggerDetails.isLoggable(BasicLevel.DEBUG)) {
                loggerDetails.log(BasicLevel.DEBUG, "No tagged component with id " + TAG_CSI_SEC_MECH_LIST.value);
            }
            return;

        }

        // Nothing to do if the component is not here
        if (taggedComponent == null) {
            return;
        }


        // Extract infos from the received TaggedComponent
        Any pAny = null;
        try {
            pAny = codec.decode_value(taggedComponent.component_data, CompoundSecMechListHelper.type());
        } catch (FormatMismatch fm) {
            logger.log(BasicLevel.ERROR, "Format mismatch while decoding value :" + fm.getMessage());
            return;
        } catch (TypeMismatch tm) {
            logger.log(BasicLevel.ERROR, "Type mismatch while decoding value :" + tm.getMessage());
            return;
        }

        // TODO : there can have several compound sech mech
        // For now, take first
        CompoundSecMechList compoundSecMechList = CompoundSecMechListHelper.extract(pAny);
        CompoundSecMech compoundSecMech = null;
        if (compoundSecMechList.mechanism_list.length > 0) {
            compoundSecMech = compoundSecMechList.mechanism_list[0];
        } else  {
            // no compound sec mech received !
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "No coumpound sec mech in the list.");
            }
            return;
        }


        /**
         * Now, build the EstablishContext message with stateless mode [17]
         * EstablishContextMessage format Interoperability EstablishContext <br>
         * <br>
         * An EstablishContext message is sent by a CSS to establish a SAS
         * context with a TSS. The SAS context and the context identifier
         * allocated by the CSS to refer to it are scoped to the transport layer
         * connection or association over which the CSS and TSS are
         * communicating. When an association is dismantled, all SAS contexts
         * scoped to the connection shall be invalidated and may be discarded.
         * The EstablishContext message contains the following fields: <br>
         * <ul>
         * <li>client_context_id The CSS allocated identifier for the security
         * attribute service context. A stateless CSS shall set the
         * client_context_id to 0, indicating to the TSS that it is stateless. A
         * stateful CSS may allocate a nonzero client_context_id.</li>
         * <li>authorization_token May be used by a CSS to push privilege
         * information to a TSS. A CSS may use this token to send proxy
         * privileges to a TSS as a means to enable the target to issue calls as
         * the client.</li>
         * <li>identity_token Carries a representation of the invocation
         * identity for the call (that is, the identity under which the call is
         * to be authorized). The identity_token carries a representation of the
         * invocation identity in one of the following forms:
         * <ol>
         * <li>A typed mechanism-specific representation of a principal name
         * </li>
         * <li>A chain of identity certificates representing the subject and a
         * chain of verifying authorities</li>
         * <li>A distinguished name</li>
         * <li>The anonymous principal identity (a type, not a name)</li>
         * </ol>
         * An identity_token is used to assert a caller identity when that
         * identity differs from the identity proven by authentication in the
         * authentication layer(s). If the caller identity is intended to be the
         * same as that established in the authentication layer(s), then it does
         * not need to be asserted in an identity_token.</li>
         * <li>client_authentication_token Carries a mechanism-specific GSS
         * initial context token that authenticates the client to the TSS. It
         * contains a mechanism type identifier and the mechanism-specific
         * evidence (that is, the authenticator) required by the TSS to
         * authenticate the client. When an initial context token contains
         * private credentials, such as a password, this message may be safely
         * sent only after a confidential connection with a trusted TSS has been
         * established. The determination of when it is safe to send a client
         * authentication token in an EstablishContext message shall be
         * considered in the context of the CORBA location-binding paradigm for
         * persistent objects (where an invocation may be location forwarded by
         * a location daemon to the target object).</li>
         * </ul>
         */

        long clientContextId = Csiv2Const.STATELESS_CONTEXT_ID;
        AuthorizationElement[] withoutAuthorizationToken = new AuthorizationElement[0];

        IdentityToken identityToken = null;

        // Anonymous
        IdentityToken anonymousIdentityToken = new IdentityToken();
        anonymousIdentityToken.anonymous(true);

        // Absent
        IdentityToken absentIdentityToken = new IdentityToken();
        absentIdentityToken.absent(true);


        byte[] clientAuthenticationToken = Csiv2Const.EMPTY_BYTES;


        // Test what we need to send (depending of the support)
        // see 16-5.2 section

        // Client authentication token
        if ((compoundSecMech.as_context_mech.target_requires & EstablishTrustInClient.value) == EstablishTrustInClient.value) {
            pAny = null;
            try {
                pAny = ORBHelper.getOrb().create_any();
            } catch (Csiv2InterceptorException csie) {
                logger.log(BasicLevel.ERROR, "Cannot get orb for any = " + csie.getMessage());
                return;
            }
            InitialContextToken initialContextToken = null;
            try {
                initialContextToken = SecurityContextHelper.getInstance().getInitialContextToken();
            } catch (UnsupportedEncodingException uee) {
                logger.log(BasicLevel.ERROR, "Unsupported encoding for UTF8" + uee.getMessage());
                return;
            }
            InitialContextTokenHelper.insert(pAny, initialContextToken);
            byte[] contextData = null;

            try {
                contextData = codec.encode_value(pAny);
            } catch (InvalidTypeForEncoding itfe) {
                logger.log(BasicLevel.ERROR, "Cannot encode a given any corba object : " + itfe.getMessage());
                return;
            }

            try {
                clientAuthenticationToken = GSSHelper.encodeToken(contextData);
            } catch (IOException ioe) {
                logger.log(BasicLevel.ERROR, "Cannot encode client authentication token : " + ioe.getMessage());
                return;
            }
        }


        // Identity token case
        if ((compoundSecMech.sas_context_mech.target_supports & IdentityAssertion.value) == IdentityAssertion.value) {
            pAny = null;
            try {
                pAny = ORBHelper.getOrb().create_any();
            } catch (Csiv2InterceptorException csie) {
                logger.log(BasicLevel.ERROR, "Cannot get orb for any = " + csie.getMessage());
                return;
            }


            // Insert username
            String identity = SecurityContextHelper.getInstance().getIdentityToken();
            byte[] name = GSSHelper.encodeExported(identity);
            byte[] principalName = null;
            GSS_NT_ExportedNameHelper.insert(pAny, name);
            try {
                principalName = codec.encode_value(pAny);
            } catch (InvalidTypeForEncoding itfe) {
                logger.log(BasicLevel.ERROR, "Cannot encode a given any corba object : " + itfe.getMessage());
                return;
            }


            // Put name in the token
            identityToken = new IdentityToken();
            identityToken.principal_name(principalName);

        }

        // No identity was set (principal name), so use absent identity
        if (identityToken == null) {
            identityToken = absentIdentityToken;
        }

        // if absent and no client auth token, don't do anything
        if (identityToken == absentIdentityToken && clientAuthenticationToken == Csiv2Const.EMPTY_BYTES) {
            return;
        }


        EstablishContext establishContext = new EstablishContext(clientContextId, withoutAuthorizationToken,
                identityToken, clientAuthenticationToken);





        /**
         * And then, this message should be added. see 16.2.1 The Security
         * Attribute Service Context Element [10] This specification defines a
         * new GIOP service context element type, the security attribute service
         * (SAS) element. <br>
         * [11] The SAS context element may be used to associate any or all of
         * the following contexts with GIOP request and reply messages: "
         * Identity context, to be accepted based on trust " Authorization
         * context, including authorization-based delegation context " Client
         * authentication context <br>
         * [12] A new context_id has been defined for the SAS element. const
         * ServiceId SecurityAttributeService = 15
         */
        try {
            pAny = ORBHelper.getOrb().create_any();
        } catch (Csiv2InterceptorException csie) {
            logger.log(BasicLevel.ERROR, "Cannot get orb for any = " + csie.getMessage());
            return;
        }

        // Generate contextData of service context with EstablishContext
        SASContextBody sasContextBody = new SASContextBody();
        sasContextBody.establish_msg(establishContext);
        SASContextBodyHelper.insert(pAny, sasContextBody);
        byte[] contextData = null;

        try {
            contextData = codec.encode_value(pAny);
        } catch (InvalidTypeForEncoding itfe) {
            logger.log(BasicLevel.ERROR, "Cannot encode a given any corba object : " + itfe.getMessage());
            return;
        }

        // build service context and add it
        ServiceContext serviceContext = new ServiceContext(SecurityAttributeService.value, contextData);
        ri.add_request_service_context(serviceContext, Csiv2Const.REPLACE_SECURITY_ATTRIBUTE_SERVICE);

    }

    /**
     * Provides an opportunity to destroy this interceptor.
     */
    public void destroy() {
        // TODO Auto-generated method stub

    }

    /**
     * Returns the name of the interceptor.
     * @return the name of the interceptor.
     */
    public String name() {
        return NAME;
    }

}