/**
 * 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: Csiv2ServerInterceptor.java 15428 2008-10-07 11:20:29Z sauthieg $
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.security.iiop;

import org.omg.CORBA.Any;
import org.omg.CORBA.BAD_PARAM;
import org.omg.CORBA.NO_PERMISSION;
import org.omg.CSI.CompleteEstablishContext;
import org.omg.CSI.EstablishContext;
import org.omg.CSI.GSS_NT_ExportedNameHelper;
import org.omg.CSI.ITTPrincipalName;
import org.omg.CSI.IdentityToken;
import org.omg.CSI.MTEstablishContext;
import org.omg.CSI.MTMessageInContext;
import org.omg.CSI.SASContextBody;
import org.omg.CSI.SASContextBodyHelper;
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.CodecPackage.FormatMismatch;
import org.omg.IOP.CodecPackage.InvalidTypeForEncoding;
import org.omg.IOP.CodecPackage.TypeMismatch;
import org.omg.PortableInterceptor.ForwardRequest;
import org.omg.PortableInterceptor.ServerRequestInfo;
import org.omg.PortableInterceptor.ServerRequestInterceptor;

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 server side
 * @see Csiv2 spec : A target security service (TSS) is the security service associated with the ORB that hosts the target object.
 * @see Common Secure Interoperability V2 Specification (July 23,2001)
 * @author Florent Benoit
 */
public class Csiv2ServerInterceptor extends org.omg.CORBA.LocalObject implements ServerRequestInterceptor {

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

 /**
     * 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 Csiv2ServerInterceptor(Codec codec, Logger logger, Logger loggerDetails) {
        this.codec = codec;
        this.logger = logger;
        this.loggerDetails = loggerDetails;
    }

    /**
     * Allows an Interceptor to query request information after all the
     * information, including operation parameters, are available. This
     * interception point shall execute in the same thread as the target
     * invocation.
     * @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_request(ServerRequestInfo ri) throws ForwardRequest {

        // Is there a security attribute service context (Csiv2 16.2 protocol message definition)
        ServiceContext receiveServiceContext  = null;
        try {
            // Csiv2 16.9.1 / Type defined for security attribute service
            receiveServiceContext = ri.get_request_service_context(SecurityAttributeService.value);
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "Got security service context = " + receiveServiceContext);
            }
        } catch (BAD_PARAM e) {
            if (loggerDetails.isLoggable(BasicLevel.DEBUG)) {
                loggerDetails.log(BasicLevel.DEBUG, "No security service context found");
            }
        }

        // No serviceContext, just return
        if (receiveServiceContext == null) {
            return;
        }

        // Analyze service context
        SASContextBody receivedSASContextBody = null;
        Any receiveAny = null;
        try {
            receiveAny = codec.decode_value(receiveServiceContext.context_data, SASContextBodyHelper.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;
        }
        receivedSASContextBody = SASContextBodyHelper.extract(receiveAny);
        if (receivedSASContextBody == null) {
            logger.log(BasicLevel.ERROR, "Received Sascontext body is null");
            return;
        }
        short discriminator = receivedSASContextBody.discriminator();

        if (discriminator == MTEstablishContext.value) {
            // Analyze the establish context message
            EstablishContext receivedEstablishContext = receivedSASContextBody.establish_msg();

            // client authentication token
            byte[] clientAuthenticationToken = receivedEstablishContext.client_authentication_token;
            // identity token
            IdentityToken identityToken = receivedEstablishContext.identity_token;

            // client authentication token case
            if (clientAuthenticationToken != null && clientAuthenticationToken.length != 0) {
                Any pAny = null;
                try {
                    pAny = codec.decode_value(GSSHelper.decodeToken(receivedEstablishContext.client_authentication_token), InitialContextTokenHelper.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;
                }
                InitialContextToken initialContextToken = InitialContextTokenHelper.extract(pAny);
                String userName = new String(initialContextToken.username);
                String password = new String(initialContextToken.password);
                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, "Received InitialContextToken, login = '" + userName + "' and password = '" + password + "'.");
                }
                SecurityContextHelper.getInstance().loginAuthenticationToken(userName, password);

            } else if (identityToken != null) { // identity token case
                try {
                    // Principal name case
                    if (identityToken.discriminator() == ITTPrincipalName.value) {
                        Any a = codec.decode_value(receivedEstablishContext.identity_token.principal_name(), GSS_NT_ExportedNameHelper.type());
                        byte[] encodedName = GSS_NT_ExportedNameHelper.extract(a);

                        // Decode the principal name
                        String principalName = GSSHelper.decodeExported(encodedName);
                        if (logger.isLoggable(BasicLevel.DEBUG)) {
                            logger.log(BasicLevel.DEBUG, "Received identityToken, principalName = " + principalName);
                        }
                        SecurityContextHelper.getInstance().loginIdentiyToken(principalName);
                    }
                } catch (Exception e) {
                    logger.log(BasicLevel.ERROR, "Error = " + e.getMessage());
                    return;
                }
            }

        } else if (discriminator == MTMessageInContext.value) { // not handle
            throw new NO_PERMISSION();
        }

        // Make CompleteEstablish context message
        /**
         * CompleteEstablishContext Message Format [23] <br>
         * A CompleteEstablishContext message is sent by a TSS in response to an
         * EstablishContext message to indicate that the context was
         * established. The CompleteEstablishContext message contains the
         * following fields:
         * <ul>
         * <li>client_context_id The CSS allocated identifier for the security
         * attribute context. It is returned by the target so that a stateful
         * CSS can link this message to the EstablishContext request. A TSS
         * shall always return the value of the client_context_id it received in
         * the EstablishContext message.</li>
         * <li>context_stateful The value returned by the TSS to indicate
         * whether or not the established context is stateful, and thus
         * reusable. A stateless TSS shall always return false. A stateful TSS
         * shall return true if the established context is reusable. Otherwise a
         * stateful TSS shall return false.</li>
         * <li>final_context_token The GSS mechanism-specific final context
         * token that is returned by a TSS if the client requests mutual
         * authentication. When a TSS accepts an EstablishContext message
         * containing an initial context token that requires mutual
         * authentication, the TSS shall return a mechanism-specific final
         * context token. Not all GSS mechanisms support mutual authentication,
         * and thus not all responses to initial context tokens may include
         * final (or output) context tokens.5 When a CompleteEstablishContext
         * message contains a final_context_token, the token shall be applied
         * (with GSS_Init_sec_context) to the client-side GSS state machine
         * </li>
         * </ul>
         */
        CompleteEstablishContext completeEstablishContext = new CompleteEstablishContext(Csiv2Const.STATELESS_CONTEXT_ID, Csiv2Const.STATEFUL_MODE, Csiv2Const.EMPTY_BYTES);


        /**
         * 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
         */
        Any pAny = null;
        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.complete_msg(completeEstablishContext);
        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_reply_service_context(serviceContext, Csiv2Const.REPLACE_SECURITY_ATTRIBUTE_SERVICE);


    }

    /**
     * Allows the interceptor to process service context information.
     * @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_request_service_contexts(ServerRequestInfo ri) throws ForwardRequest {
        // TODO Auto-generated method stub

    }

    /**
     * Allows an Interceptor to query the exception information and modify
     * the reply service context before the exception is thrown to the client.
     * When an exception occurs, this interception point is called. This
     * interception point shall execute in the same thread as the target
     * invocation.
     * @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 send_exception(ServerRequestInfo ri) throws ForwardRequest {
        // TODO Auto-generated method stub

    }

    /**
     * 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 send_other(ServerRequestInfo ri) throws ForwardRequest {
        // TODO Auto-generated method stub

    }

    /**
     * Allows an Interceptor to query reply information and modify the
     * reply service context after the target operation has been invoked
     * and before the reply is returned to the client. This interception
     * point shall execute in the same thread as the target invocation.
     * @param ri Information about the current request being intercepted.
     */
    public void send_reply(ServerRequestInfo ri) {

    }

    /**
     * Provides an opportunity to destroy this interceptor.
     */
    public void destroy() {

    }

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