/*
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999 Bull S.A.
 * Contact: jonas-team@objectweb.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$
 * --------------------------------------------------------------------------
 */

package org.objectweb.jonas.jtests.clients.security;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicPublisher;
import javax.jms.TopicSession;
import javax.naming.NamingException;
import javax.rmi.PortableRemoteObject;

import org.ow2.jonas.lib.security.context.SecurityContext;
import org.ow2.jonas.lib.security.context.SecurityCurrent;

import org.objectweb.jonas.jtests.beans.secured.BaseS;
import org.objectweb.jonas.jtests.beans.secured.BaseSHome;
import org.objectweb.jonas.jtests.beans.secured.Session1;
import org.objectweb.jonas.jtests.beans.secured.Session1Home;
import org.objectweb.jonas.jtests.util.JTestCase;
import junit.framework.Test;
import junit.framework.TestSuite;

/**
 * Security Management Suite with a stateless session as first bean
 * Test the run-as element
 *
 * @author Florent Benoit
 *
 */

public class F_RunAs extends JTestCase {
 

    /**
     * Bean RunAs
     */
    private static String BEAN_HOME_RUNAS = "securedBaseRunAsSLHome";

    /**
     * Bean without RunAs
     */
    private static String BEAN_HOME_NO_RUNAS = "securedBaseNoRunAsSLHome";


    /**
     * Name of the principal 1
     */
    protected static String PRINCIPAL1_NAME = "principal1";

    /**
     * Name of the principal 2
     */
    protected static String PRINCIPAL2_NAME = "principal2";

    /**
     * Name of the role 1
     */
    protected static String ROLE1_NAME = "role1";

    /**
     * Name of the role 2
     */
    protected static String ROLE2_NAME = "role2";

    /**
     * Home runAs
     */
    protected static BaseSHome runAsHome = null;

    /**
     * Home no runAs
     */
    protected static BaseSHome noRunAsHome = null;

    /**
     * Current
     */
    protected static SecurityCurrent current = null;

    /**
     * principal 1
     */
    protected static SecurityContext principal1 = null;

    /**
     * principal 2
     */
    protected static SecurityContext principal2 = null;


    /**
     * Constructor
     */
    public F_RunAs(String name) {
        super(name);
    }

    /**
     * Return the Home runAs
     */
    public BaseSHome getRunAsHome() {
        if (runAsHome == null) {
            try {
                runAsHome = (BaseSHome) PortableRemoteObject.narrow(ictx.lookup(BEAN_HOME_RUNAS), BaseSHome.class);
            } catch (NamingException e) {
                fail("Cannot get bean home " + BEAN_HOME_RUNAS);
            }
        }
        return runAsHome;
    }

    /**
     * Return the Home no runAs
     */
    public BaseSHome getNoRunAsHome() {
        if (noRunAsHome == null) {
            try {
                noRunAsHome = (BaseSHome) PortableRemoteObject.narrow(ictx.lookup(BEAN_HOME_NO_RUNAS), BaseSHome.class);
            } catch (NamingException e) {
                fail("Cannot get bean home " + BEAN_HOME_NO_RUNAS);
            }
        }
        return noRunAsHome;
    }

    public BaseS getBaseRunAs() throws Exception {
        return getRunAsHome().create();
    }

    public BaseS getBaseNoRunAs() throws Exception{
        return getNoRunAsHome().create();
    }

    /**
     * init environment:
     * - load beans
     */
    protected void setUp() {
        super.setUp();
        if (current == null) {
            current = SecurityCurrent.getCurrent();
            String[] roles1 = new String[]{ROLE1_NAME};
            principal1 = new SecurityContext(PRINCIPAL1_NAME, roles1);
            String[] roles2 = new String[]{ROLE2_NAME};
            principal2 = new SecurityContext(PRINCIPAL2_NAME, roles2);
        }
        useBeans("secured", true);
    }


    /**
     * This suite is all BMP test cases
     */
    public static Test suite() {
        return new TestSuite(F_RunAs.class);
    }

    public static void main (String args[]) {
        String testtorun = null;
        // Get args
        for (int argn = 0; argn < args.length; argn++) {
            String s_arg = args[argn];
            Integer i_arg;
            if (s_arg.equals("-n")) {
                testtorun = args[++argn];
            }
        }
        if (testtorun == null) {
            junit.textui.TestRunner.run(suite());
        } else {
            junit.textui.TestRunner.run(new F_RunAs(testtorun));
        }
    }



    /**
     * Test the call on a bean which call another bean without any runAs
     * The principal must be propagated
     */
    public void testNoRunAsAtAll() throws Exception {
        current.setSecurityContext(principal1);
        BaseS sl = getBaseNoRunAs();
        assertEquals(PRINCIPAL1_NAME, sl.getPrincipalName());
        assertTrue(sl.isCallerInRole(ROLE1_NAME));
        sl.callBeanNoRunAsWithRole1();
        sl.remove();
    }



    /**
     * Test the call on a bean which call another bean.
     * First bean need to have a run-as access, but not the second
     * The principal must be propagated
     */
    public void testRunAsAndNoRunAs() throws Exception {
        current.setSecurityContext(principal2);
        BaseS sl = null;
        try {
            sl = getBaseRunAs();
        } catch (Exception e) {
            fail("Create failed. Role used to access this bean must be role2");
        }
        assertEquals(PRINCIPAL2_NAME, sl.getPrincipalName());
        assertTrue(sl.isCallerInRole(ROLE2_NAME));
        boolean b = sl.callBeanNoRunAsWithRole2();
        if (!b) {
            fail("Cannot call another bean as role for calling the method must be role1 (run-as on the current bean) and not role2 (principal role)");
        }
        sl.remove();
    }

    /**
     * Test the run-as with a Timer
     * First bean implements TimedObject and has the run-as set.
     * Timeout method calls another bean method protected
     * @throws Exception Unexpected Test error
     */
    public void testRunAsOnTimer() throws Exception {
        current.setSecurityContext(principal2);
        BaseS sl = getBaseRunAs();
        int duration = 5;
        try {
            int oldval = sl.getTimerCount();
            sl.setTimer(duration, 0, 2);
            sleep(2000);
            assertEquals("timer expired too quickly", oldval, sl.getTimerCount());
            sleep(4000);
            assertEquals("timer did not expired", oldval + 1, sl.getTimerCount());
        } finally {
            sl.remove();
        }
    }
    
    /**
     * Test the call on a bean which call another bean.
     * First bean don't need to have a run-as access, but the second does
     * The principal must be propagated
     */
    public void testnoRunAsAndRunAs() throws Exception {
        current.setSecurityContext(principal1);
        BaseS sl = null;
        sl = getBaseNoRunAs();
        assertEquals(PRINCIPAL1_NAME, sl.getPrincipalName());
        assertTrue(sl.isCallerInRole(ROLE1_NAME));
        boolean b = sl.callBeanRunAsWithRole1();
        if (!b) {
            fail("Current role is role1 and the bean which is called need to have role2");
        }
        sl.remove();
    }



    /**
     * Test the call on a bean which call another bean.
     * First bean  need to have a run-as access and the second too
     * First run-as is role1 ,second run-as is role3
     * so the test works if there is an access denied in second bean
     * The principal must be propagated
     */
    public void testRunAsChain() throws Exception {
        current.setSecurityContext(principal2);
        BaseS sl = null;
        try {
            sl = getBaseRunAs();
        } catch (Exception e) {
            fail("Create failed. Maybe role used is role1 but it must be role2 as this bean has got a run-as attribute with role2");
        }
        assertEquals(PRINCIPAL2_NAME, sl.getPrincipalName());
        assertTrue(sl.isCallerInRole(ROLE2_NAME));
        boolean b = sl.callBeanRunAsWithRole2();
        if (!b) {
            fail("Current role is role2 and the bean which is called has got a run as with role1. The next bean need role 2.");
        }
        sl.remove();
    }


    /**
     * Test the call on a bean which call another bean, etc
     * First bean  need role2 and has a run-as
     * After calling a runas bean, it call a no-run as bean
     * First run-as is role2 ,second run-as is role3 and last call is done on a without runas
     * The principal must be propagated
     */
    public void testRunAsMultipleChain() throws Exception {
        current.setSecurityContext(principal2);
        BaseS sl = null;
        try {
            sl = getBaseRunAs();
        } catch (Exception e) {
            fail("Create failed. Maybe role used is role1 but it must be role2 as this bean has got a run-as attribute with role2");
        }
        assertEquals(PRINCIPAL2_NAME, sl.getPrincipalName());
        assertTrue(sl.isCallerInRole(ROLE2_NAME));
        boolean b = sl.callBeanRunAsWithRole2();
        if (!b) {
            fail("Current role is role2 and the bean which is called has got a run as with role1. The next bean require role1 so it must work");
        }

        b = sl.callBeanNoRunAsWithRole2();
        if (!b) {
            fail("Current role is role2 and the bean which is called has got a run as with role1. The next bean require role1 so it must work");
        }

        sl.remove();
    }

    /**
     * Test call on protected beans, some beans have run-as roles
     * It also use LoginContext to authenticate
     * This test come from user Alexander Daryin
     * This testcase test the problem of the order in ejb-jar.xml of the permissions
     */
    public void testRunAsAndSecurityOrderDeclaration() throws Exception {
        current.setSecurityContext(principal1);
        final Session1Home home = (Session1Home) PortableRemoteObject.narrow(ictx.lookup("securedSession1EJB"), Session1Home.class);
        final Session1 bean = home.create();
        String resultTest = bean.test();
        if (!("value".equals(resultTest))) {
            fail("The return value must be 'value' instead of '" + resultTest + "'");
        }
        

    }


    /**
     * Send a message to a MDB which create another bean with its run-as role
     * MDB send to a queue the answer to see if the test is ok or fail
     */
    public void testRunAsJms() throws Exception {
        current.setSecurityContext(principal2);

        TopicConnectionFactory tcf = null;
        TopicConnection tc = null;
        // Lookup Connection Factories
        try {
            tcf = (TopicConnectionFactory) ictx.lookup("JTCF");
        } catch (NamingException e) {
            fail("Cannot lookup Connection Factories");
        }

        // Create Connections
        try {
            tc = tcf.createTopicConnection();
        } catch (JMSException e) {
            fail("Cannot create connections");
        }

        TopicSession ss = null;
        try {
            ss = tc.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
        } catch (JMSException e) {
            fail("Cannot create Session: " + e);
        }

        Topic topic = null;
        try {
            topic = (Topic) ictx.lookup("runAsTopic");
        } catch (Exception e) {
            fail("Cannot lookup Topic: " + e);
        }

        
        // Create the TopicPublisher
        TopicPublisher publisher = null;
        try {
            publisher = ss.createPublisher(topic);
        } catch (JMSException e) {
            fail("Cannot create TopicPublisher: " + e);
        }

        // Publish messages on the queue
        try {
            MapMessage mess = ss.createMapMessage();
            mess.setString("Id", "test");
            publisher.publish(mess);
        } catch (JMSException e) {
            fail("Cannot send message: " + e);
        }
        
        // No close else it fails with JDK 1.3
        try {
            ss.close();
            tc.close();
        } catch (JMSException e) {
            fail("Cannot close session: "+e);
        }
        

        // Now receive the message to test if the test is ok or not
        String msgtxt = null;
        try {
            ConnectionFactory cf = (ConnectionFactory) ictx.lookup("JCF");
            Queue queue = (Queue) ictx.lookup("sampleQueue");
            Connection conn = cf.createConnection();
            Session sess = conn.createSession(true, Session.AUTO_ACKNOWLEDGE);
            MessageConsumer mc = sess.createConsumer((Destination) queue);
            conn.start();
            Message message = (Message) mc.receive(10000);
            if (message == null) {
                fail("Can not receive message");
            }
            msgtxt = message.getStringProperty("testRunAsJms");
            sess.close();
            conn.close();
        } catch (Exception e) {
            fail("Can not get answer of the jms " + e);
        }

        if (msgtxt == null) {
            fail("No message received from the bean");
        }

        if (!msgtxt.equals("ok")) {
            fail("The test is not ok : " + msgtxt);
        }


    }

}
